Skip to content

Commit 7ae457e

Browse files
authored
feat: enable retry configuration (#702)
* feat: enable configuration of the retry strategy through environment variables, system properties, and profiles. * Address ktlint errors * Use imperative present tense * Parse max_attempts into Int and retry_mode into RetryMode * Handle Int and RetryMode types in retryMode resolution * Use `assertFailsWith` instead of `assertThrows` * Remove needless blank line * Use null coalescing and throw ConfigurationException * Throw ConfigurationException for invalid maxAttempts * Expect ConfigurationException to be thrown * Add KDocs * tickle ci
1 parent 652a176 commit 7ae457e

File tree

9 files changed

+373
-0
lines changed

9 files changed

+373
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"id": "0d2cc7ea-39dc-11ed-a261-0242ac120002",
3+
"type": "feature",
4+
"description": "Enable configurability of the retry strategy through environment variables, system properties, and AWS profiles.",
5+
"issues": ["#486"]
6+
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
package aws.sdk.kotlin.runtime.config
77

8+
import aws.sdk.kotlin.runtime.ConfigurationException
89
import aws.sdk.kotlin.runtime.InternalSdkApi
10+
import aws.sdk.kotlin.runtime.config.retries.RetryMode
911
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
1012

1113
// NOTE: The JVM property names MUST match the ones defined in the Java SDK for any setting added.
@@ -130,6 +132,17 @@ public sealed class AwsSdkSetting<T>(
130132
* An authorization token to pass to a container metadata service.
131133
*/
132134
public object AwsContainerAuthorizationToken : AwsSdkSetting<String>("AWS_CONTAINER_AUTHORIZATION_TOKEN", "aws.containerAuthorizationToken", null)
135+
136+
/**
137+
* The maximum number of request attempts to perform. This is one more than the number of retries, so
138+
* aws.maxAttempts = 1 will have 0 retries.
139+
*/
140+
public object AwsMaxAttempts : AwsSdkSetting<Int>("AWS_MAX_ATTEMPTS", "aws.maxAttempts")
141+
142+
/**
143+
* Which RetryMode to use for the default RetryPolicy, when one is not specified at the client level.
144+
*/
145+
public object AwsRetryMode : AwsSdkSetting<RetryMode>("AWS_RETRY_MODE", "aws.retryMode")
133146
}
134147

135148
/**
@@ -148,6 +161,8 @@ public inline fun <reified T> AwsSdkSetting<T>.resolve(platform: PlatformEnviron
148161
Int::class -> strValue.toInt()
149162
Long::class -> strValue.toLong()
150163
Boolean::class -> strValue.toBoolean()
164+
RetryMode::class -> RetryMode.values().firstOrNull { it.name.equals(strValue, ignoreCase = true) }
165+
?: throw ConfigurationException("Retry mode $strValue is not supported, should be one of: ${RetryMode.values().joinToString(", ")}")
151166
else -> error("conversion to ${T::class} not implemented for AwsSdkSetting")
152167
}
153168
return typed as? T

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
package aws.sdk.kotlin.runtime.config.profile
77

8+
import aws.sdk.kotlin.runtime.ConfigurationException
9+
import aws.sdk.kotlin.runtime.config.retries.RetryMode
10+
811
/**
912
* The properties and name of an AWS configuration profile.
1013
*
@@ -53,3 +56,21 @@ public val AwsProfile.roleArn: String?
5356
*/
5457
public val AwsProfile.sourceProfile: String?
5558
get() = this["source_profile"]
59+
60+
/**
61+
* The maximum number of request attempts to perform. This is one more than the number of retries, so
62+
* aws.maxAttempts = 1 will have 0 retries.
63+
*/
64+
public val AwsProfile.maxAttempts: Int?
65+
get() = this["max_attempts"]?.run {
66+
toIntOrNull() ?: throw ConfigurationException("Failed to parse maxAttempts $this as an integer")
67+
}
68+
69+
/**
70+
* Which [RetryMode] to use for the default RetryPolicy, when one is not specified at the client level.
71+
*/
72+
public val AwsProfile.retryMode: RetryMode?
73+
get() = this["retry_mode"]?.run {
74+
RetryMode.values().firstOrNull { it.name.equals(this, ignoreCase = true) }
75+
?: throw ConfigurationException("Retry mode $this is not supported, should be one of: ${RetryMode.values().joinToString(", ")}")
76+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.sdk.kotlin.runtime.config.retries
7+
8+
import aws.sdk.kotlin.runtime.ConfigurationException
9+
import aws.sdk.kotlin.runtime.InternalSdkApi
10+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
11+
import aws.sdk.kotlin.runtime.config.profile.loadActiveAwsProfile
12+
import aws.sdk.kotlin.runtime.config.profile.maxAttempts
13+
import aws.sdk.kotlin.runtime.config.profile.retryMode
14+
import aws.sdk.kotlin.runtime.config.resolve
15+
import aws.smithy.kotlin.runtime.retries.RetryStrategy
16+
import aws.smithy.kotlin.runtime.retries.StandardRetryStrategy
17+
import aws.smithy.kotlin.runtime.retries.StandardRetryStrategyOptions
18+
import aws.smithy.kotlin.runtime.util.Platform
19+
import aws.smithy.kotlin.runtime.util.PlatformProvider
20+
import aws.smithy.kotlin.runtime.util.asyncLazy
21+
22+
/**
23+
* Attempt to resolve the retry strategy used to make requests by fetching the max attempts and retry mode. Currently,
24+
* we only support the legacy and standard retry modes.
25+
*/
26+
@InternalSdkApi
27+
public suspend fun resolveRetryStrategy(platformProvider: PlatformProvider = Platform): RetryStrategy {
28+
val profile = asyncLazy { loadActiveAwsProfile(platformProvider) }
29+
30+
val maxAttempts = AwsSdkSetting.AwsMaxAttempts.resolve(platformProvider)
31+
?: profile.get().maxAttempts
32+
?: StandardRetryStrategyOptions.Default.maxAttempts
33+
34+
if (maxAttempts < 1) { throw ConfigurationException("max attempts was $maxAttempts, but should be at least 1") }
35+
36+
val retryMode = AwsSdkSetting.AwsRetryMode.resolve(platformProvider)
37+
?: profile.get().retryMode
38+
?: RetryMode.STANDARD
39+
40+
return when (retryMode) {
41+
RetryMode.STANDARD, RetryMode.LEGACY -> StandardRetryStrategy(StandardRetryStrategyOptions(maxAttempts))
42+
RetryMode.ADAPTIVE -> throw NotImplementedError("Retry mode $retryMode is not implemented yet. https://github.com/awslabs/aws-sdk-kotlin/issues/701")
43+
}
44+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.sdk.kotlin.runtime.config.retries
7+
8+
/**
9+
* The retry mode to be used for the client's retry strategy.
10+
*/
11+
public enum class RetryMode {
12+
/**
13+
* The legacy retry mode is supported for compatibility with other SDKs and existing configurations for them,
14+
* but it works exactly the same as the standard retry mode for this SDK.
15+
*/
16+
LEGACY,
17+
18+
/**
19+
* The standard retry mode. With this, the client will use the [StandardRetryStrategy][aws.smithy.kotlin.runtime.retries.StandardRetryStrategy]
20+
*/
21+
STANDARD,
22+
23+
/**
24+
* Not implemented yet. https://github.com/awslabs/aws-sdk-kotlin/issues/701
25+
*/
26+
ADAPTIVE;
27+
}

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/config/AwsSdkSettingTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package aws.sdk.kotlin.runtime.config
22

3+
import aws.sdk.kotlin.runtime.config.retries.RetryMode
34
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
45
import kotlin.test.Test
56
import kotlin.test.assertEquals
@@ -51,6 +52,24 @@ class AwsSdkSettingTest {
5152
assertEquals(true, actual)
5253
}
5354

55+
@Test
56+
fun itResolvesMaxAttemptsFromEnvironmentVariables() {
57+
val expected = 5
58+
val testPlatform = mockPlatform(mapOf("AWS_MAX_ATTEMPTS" to expected.toString()), mapOf())
59+
60+
val actual = AwsSdkSetting.AwsMaxAttempts.resolve(testPlatform)
61+
assertEquals(expected, actual)
62+
}
63+
64+
@Test
65+
fun itResolvesRetryModeFromSystemProperties() {
66+
val expected = RetryMode.LEGACY
67+
val testPlatform = mockPlatform(mapOf(), mapOf("aws.retryMode" to "legacy"))
68+
69+
val actual = AwsSdkSetting.AwsRetryMode.resolve(testPlatform)
70+
assertEquals(expected, actual)
71+
}
72+
5473
private fun mockPlatform(env: Map<String, String>, jvmProps: Map<String, String>): PlatformEnvironProvider {
5574
return object : PlatformEnvironProvider {
5675
override fun getAllEnvVars(): Map<String, String> = env

0 commit comments

Comments
 (0)