diff --git a/.changes/next-release/feature-AWSSDKforJavav2-8c493dd.json b/.changes/next-release/feature-AWSSDKforJavav2-8c493dd.json new file mode 100644 index 000000000000..e626d024ecf9 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-8c493dd.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Update the default retry strategy to standard based on [Modernizing the default retry strategy](https://aws.amazon.com/blogs/developer/updating-aws-sdk-defaults-aws-sts-service-endpoint-and-retry-strategy/)" +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java index fdfe4fa68a82..872fa9952e85 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java @@ -130,7 +130,7 @@ public static Resolver resolver() { * Allows customizing the variables used during determination of a {@link RetryMode}. Created via {@link #resolver()}. */ public static class Resolver { - private static final RetryMode SDK_DEFAULT_RETRY_MODE = LEGACY; + private static final RetryMode SDK_DEFAULT_RETRY_MODE = STANDARD; private Supplier profileFile; private String profileName; diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java index ecd553b4d267..95f19a45564c 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java @@ -96,8 +96,8 @@ public void testRetryIoExceptionFromExecute() throws Exception { Assert.assertSame(ioException, e.getCause()); } - // Verify that we called execute 4 times. - verify(sdkHttpClient, times(4)).prepareRequest(any()); + // Verify that we called execute 3 times. + verify(sdkHttpClient, times(3)).prepareRequest(any()); } @Test @@ -119,8 +119,8 @@ public void testRetryIoExceptionFromHandler() throws Exception { Assert.assertSame(exception, e.getCause()); } - // Verify that we called execute 4 times. - verify(mockHandler, times(4)).handle(any(), any()); + // Verify that we called execute 3 times. + verify(mockHandler, times(3)).handle(any(), any()); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java index c544254dfb38..a0d4aed24cc2 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/ContentStreamProviderWireMockTest.java @@ -65,9 +65,9 @@ public void closesAllCreatedInputStreamsFromProvider() { } catch (SdkServiceException ignored) { } - // The test client uses the default retry policy so there should be 4 + // The test client uses the default retry policy so there should be 3 // total attempts and an equal number created streams - assertThat(provider.getCreatedStreams().size()).isEqualTo(4); + assertThat(provider.getCreatedStreams().size()).isEqualTo(3); for (CloseTrackingInputStream is : provider.getCreatedStreams()) { assertThat(is.isClosed()).isTrue(); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java index b5fb23c37ed4..a76b20cb2068 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java @@ -44,8 +44,8 @@ public class RetryModeTest { public static Collection data() { return Arrays.asList(new Object[] { // Test defaults - new TestData(null, null, null, null, RetryMode.LEGACY), - new TestData(null, null, "PropertyNotSet", null, RetryMode.LEGACY), + new TestData(null, null, null, null, RetryMode.STANDARD), + new TestData(null, null, "PropertyNotSet", null, RetryMode.STANDARD), // Test resolution new TestData("legacy", null, null, null, RetryMode.LEGACY), diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyMaxRetriesTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyMaxRetriesTest.java index 84e3d5ac51d7..d99049476e11 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyMaxRetriesTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyMaxRetriesTest.java @@ -44,8 +44,8 @@ public class RetryPolicyMaxRetriesTest { public static Collection data() { return Arrays.asList(new Object[] { // Test defaults - new TestData(null, null, null, null, null, 3), - new TestData(null, null, null, null, "PropertyNotSet", 3), + new TestData(null, null, null, null, null, 2), + new TestData(null, null, null, null, "PropertyNotSet", 2), // Test precedence new TestData("9", "2", "standard", "standard", "PropertySetToStandard", 8), diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyTest.java index 2d332337fc10..21b6fbd7d66b 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryPolicyTest.java @@ -89,7 +89,7 @@ public void nonRetryPolicy_shouldUseNullCondition() { @Test public void nonRetryMode_shouldUseDefaultRetryMode() { RetryPolicy policy = RetryPolicy.builder().build(); - assertThat(policy.retryMode().toString()).isEqualTo("LEGACY"); + assertThat(policy.retryMode().toString()).isEqualTo("STANDARD"); } @Test diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryStrategyMaxRetriesTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryStrategyMaxRetriesTest.java index b712a9ce4c4c..1df8134d0881 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryStrategyMaxRetriesTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryStrategyMaxRetriesTest.java @@ -46,8 +46,8 @@ public class RetryStrategyMaxRetriesTest { public static Collection data() { return Arrays.asList(new Object[] { // Test defaults - new TestData(null, null, null, null, null, 4), - new TestData(null, null, null, null, "PropertyNotSet", 4), + new TestData(null, null, null, null, null, 3), + new TestData(null, null, null, null, "PropertyNotSet", 3), // Test precedence new TestData("9", "2", "standard", "standard", diff --git a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbDefaultRetryFunctionalTest.java b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbDefaultRetryFunctionalTest.java new file mode 100644 index 000000000000..4c3436c0ac13 --- /dev/null +++ b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbDefaultRetryFunctionalTest.java @@ -0,0 +1,101 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.dynamodb; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.net.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; + +/** + * Functional tests to verify DynamoDB client retry behavior with different retry modes. + */ +@WireMockTest +class DynamoDbDefaultRetryFunctionalTest { + + private final EnvironmentVariableHelper environmentVariableHelper = new EnvironmentVariableHelper(); + private DynamoDbClient dynamoDbClient; + + @AfterEach + void tearDown() { + environmentVariableHelper.reset(); + if (dynamoDbClient != null) { + dynamoDbClient.close(); + } + } + + @ParameterizedTest + @ValueSource(strings = {"adaptive", "legacy", "standard"}) + void listTables_whenRetryModeSet_shouldAttempt9Times(String retryMode, WireMockRuntimeInfo wm) { + // Set the retry mode environment variable + environmentVariableHelper.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), retryMode); + + // Build the DynamoDB client here instead of setup so that environment variable options gets picked for each tests + dynamoDbClient = DynamoDbClient.builder() + .endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .region(Region.US_EAST_1) + .build(); + + stubFor(post(anyUrl()) + .willReturn(aResponse().withStatus(503))); + + assertThatExceptionOfType(DynamoDbException.class) + .isThrownBy(() -> dynamoDbClient.listTables()); + + int actualAttempts = wm.getWireMock().getAllServeEvents().size(); + assertThat(actualAttempts) + .as("Retry mode '%s' should result in 9 total attempts (1 initial + 8 retries)", retryMode) + .isEqualTo(9); + } + + @Test + void listTables_whenUsingDefaultRetryMode_shouldAttempt9Times(WireMockRuntimeInfo wm) { + dynamoDbClient = DynamoDbClient.builder() + .endpointOverride(URI.create(wm.getHttpBaseUrl())) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("akid", "skid"))) + .region(Region.US_EAST_1) + .build(); + stubFor(post(anyUrl()) + .willReturn(aResponse().withStatus(503))); + assertThatExceptionOfType(DynamoDbException.class) + .isThrownBy(() -> dynamoDbClient.listTables()); + + int actualAttempts = wm.getWireMock().getAllServeEvents().size(); + assertThat(actualAttempts) + .as("Default retry mode should result in 9 total attempts (1 initial + 8 retries)") + .isEqualTo(9); + } + +} diff --git a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java index bcb25c2504d6..d1c488068011 100644 --- a/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java +++ b/services/dynamodb/src/test/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicyTest.java @@ -123,7 +123,13 @@ void resolve_retryModeNotSetWithEnvNorSupplier_resolvesFromSdkDefault() { RetryStrategy retryStrategy = DynamoDbRetryPolicy.resolveRetryStrategy(sdkClientConfiguration); RetryMode retryMode = SdkDefaultRetryStrategy.retryMode(retryStrategy); - assertThat(retryMode).isEqualTo(RetryMode.LEGACY); + assertThat(retryMode).isEqualTo(RetryMode.STANDARD); } + @Test + void test_numRetries_with_defaultSettings() { + SdkClientConfiguration sdkClientConfiguration = SdkClientConfiguration.builder().build(); + RetryStrategy retryStrategy = DynamoDbRetryPolicy.resolveRetryStrategy(sdkClientConfiguration); + assertThat(retryStrategy.maxAttempts()).isEqualTo(9); + } } diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectFaultIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectFaultIntegrationTest.java index 414e8edb4727..6eb0a6c19bfb 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectFaultIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/GetObjectFaultIntegrationTest.java @@ -72,7 +72,7 @@ public void handlerThrowsRetryableException_RetriedUpToLimit() throws Exception }); assertThatThrownBy(() -> s3.getObject(getObjectRequest(), handler)) .isInstanceOf(SdkClientException.class); - assertThat(handler.currentCallCount()).isEqualTo(4); + assertThat(handler.currentCallCount()).isEqualTo(3); } @Test diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/BusinessMetricsUserAgentTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/BusinessMetricsUserAgentTest.java index 9bbb0f2eb602..9bebd6214abb 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/BusinessMetricsUserAgentTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/BusinessMetricsUserAgentTest.java @@ -76,7 +76,7 @@ public void cleanup() { private static Stream inputValues() { return Stream.of( - Arguments.of("Default values", null, Arrays.asList("D", "N", "P", "T")), + Arguments.of("Default values", null, Arrays.asList("E", "N", "P", "T")), Arguments.of("Account ID preferred mode ", AccountIdEndpointMode.PREFERRED, Arrays.asList("P", "T")), Arguments.of("Account ID disabled mode ", AccountIdEndpointMode.DISABLED, Arrays.asList("Q", "T")), Arguments.of("Account ID required mode ", AccountIdEndpointMode.REQUIRED, Arrays.asList("R", "T")) @@ -98,6 +98,7 @@ void validate_metricsString_forDifferentConfigValues(String description, assertThatThrownBy(() -> clientBuilder.build().operationWithNoInputOrOutput(r -> {}).join()).hasMessageContaining("stop"); String userAgent = assertAndGetUserAgentString(); + System.out.println("userAgent "+userAgent); expectedMetrics.forEach(expectedMetric -> assertThat(userAgent).matches(METRIC_SEARCH_PATTERN.apply(expectedMetric))); } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/ExceptionAttemptMessageBehaviorTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/ExceptionAttemptMessageBehaviorTest.java index 68195ad3b1a9..0ea62a59b6c0 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/ExceptionAttemptMessageBehaviorTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/ExceptionAttemptMessageBehaviorTest.java @@ -88,8 +88,8 @@ public void exceptionMessage_ioException_includesMultipleAttempts() { SdkClientException exception = assertThrows(SdkClientException.class, () -> callAllTypes(client)); - assertThat(exception.getMessage()).contains("SDK Attempt Count: 4"); - wireMock.verify(4, postRequestedFor(anyUrl())); + assertThat(exception.getMessage()).contains("SDK Attempt Count: 3"); + wireMock.verify(3, postRequestedFor(anyUrl())); } @Test diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/RetryStrategySetupUsingRetryMode.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/RetryStrategySetupUsingRetryMode.java index 0bb96a751531..8971776f761c 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/RetryStrategySetupUsingRetryMode.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/retry/RetryStrategySetupUsingRetryMode.java @@ -90,8 +90,8 @@ public void request_settingRetryModeInOverrideConfigurationConsumerRunTwice() { // Configuring the client using an unrelated plugin should not remember the previous settings. assertThrows(ProtocolRestJsonException.class, () -> callAllTypes(client, Collections.singletonList(unrelatedPlugin))); - // Four retries, the LEGACY retry strategy is back in. - verifyRequestCount(3 + 4); + // 3 retries, the STANDARD retry strategy is back in. + verifyRequestCount(3 + 3); } @Test diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationTest.java index 9c589ea1a1ae..daacea390097 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationTest.java @@ -111,7 +111,7 @@ public void syncClient_serviceClientConfiguration_withoutOverrideConfiguration_s assertThat(overrideConfig.apiCallAttemptTimeout()).isNotPresent(); assertThat(overrideConfig.apiCallTimeout()).isNotPresent(); assertThat(overrideConfig.retryPolicy()).isNotPresent(); - assertThat(overrideConfig.retryStrategy().get().maxAttempts()).isEqualTo(4); + assertThat(overrideConfig.retryStrategy().get().maxAttempts()).isEqualTo(3); assertThat(overrideConfig.defaultProfileFile()).hasValue(ProfileFile.defaultProfileFile()); assertThat(overrideConfig.metricPublishers()).isEmpty(); } @@ -196,7 +196,7 @@ public void asyncClient_serviceClientConfiguration_withoutOverrideConfiguration_ assertThat(overrideConfig.apiCallAttemptTimeout()).isNotPresent(); assertThat(overrideConfig.apiCallTimeout()).isNotPresent(); assertThat(overrideConfig.retryPolicy()).isNotPresent(); - assertThat(overrideConfig.retryStrategy().get().maxAttempts()).isEqualTo(4); + assertThat(overrideConfig.retryStrategy().get().maxAttempts()).isEqualTo(3); assertThat(overrideConfig.defaultProfileFile()).hasValue(ProfileFile.defaultProfileFile()); assertThat(overrideConfig.metricPublishers()).isEmpty(); } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java index 49d8e915c38b..f301cc4ee1fc 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/serviceclientconfiguration/ServiceClientConfigurationUsingPluginsTest.java @@ -102,7 +102,7 @@ void syncClient_serviceClientConfiguration_withoutOverrideConfiguration_shouldRe assertThat(overrideConfiguration.apiCallAttemptTimeout()).isNotPresent(); assertThat(overrideConfiguration.apiCallTimeout()).isNotPresent(); assertThat(overrideConfiguration.retryPolicy()).isNotPresent(); - assertThat(overrideConfiguration.retryStrategy().get().maxAttempts()).isEqualTo(4); + assertThat(overrideConfiguration.retryStrategy().get().maxAttempts()).isEqualTo(3); assertThat(overrideConfiguration.defaultProfileFile()).hasValue(ProfileFile.defaultProfileFile()); assertThat(overrideConfiguration.metricPublishers()).isEmpty(); } @@ -196,7 +196,7 @@ void asyncClient_serviceClientConfiguration_withoutOverrideConfiguration_shouldR assertThat(result.apiCallAttemptTimeout()).isNotPresent(); assertThat(result.apiCallTimeout()).isNotPresent(); assertThat(result.retryPolicy()).isNotPresent(); - assertThat(result.retryStrategy().get().maxAttempts()).isEqualTo(4); + assertThat(result.retryStrategy().get().maxAttempts()).isEqualTo(3); assertThat(result.defaultProfileFile()).hasValue(ProfileFile.defaultProfileFile()); assertThat(result.metricPublishers()).isEmpty(); }