Skip to content

Commit e2bd563

Browse files
authored
App config Revision retry after (Azure#20108)
* Adds dependency to add boostrap support in Spring Boot 2.4 * Fixed spacing * Adding Retry after for revisions endpoint. * Updated to reuse clients, updated to use HttpSatus for status code. * Added check for revisions returning at least 1 result
1 parent 0e33390 commit e2bd563

File tree

2 files changed

+197
-54
lines changed
  • sdk/appconfiguration/spring-cloud-azure-appconfiguration-config/src

2 files changed

+197
-54
lines changed

sdk/appconfiguration/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/stores/ClientStore.java

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@
88
import java.io.IOException;
99
import java.time.Duration;
1010
import java.util.ArrayList;
11+
import java.util.HashMap;
1112
import java.util.List;
1213
import java.util.Map;
1314
import java.util.Optional;
14-
import java.util.stream.Collectors;
1515

1616
import org.apache.commons.lang3.StringUtils;
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
19+
import org.springframework.http.HttpStatus;
1920
import org.springframework.lang.NonNull;
2021
import org.springframework.lang.Nullable;
2122

2223
import com.azure.core.credential.TokenCredential;
24+
import com.azure.core.http.HttpHeader;
2325
import com.azure.core.http.policy.ExponentialBackoff;
2426
import com.azure.core.http.policy.RetryPolicy;
27+
import com.azure.core.http.rest.PagedResponse;
2528
import com.azure.data.appconfiguration.ConfigurationAsyncClient;
2629
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
2730
import com.azure.data.appconfiguration.models.ConfigurationSetting;
@@ -45,21 +48,27 @@ public class ClientStore {
4548

4649
private ConfigurationClientBuilderSetup clientProvider;
4750

51+
private HashMap<String, ConfigurationAsyncClient> clients;
52+
4853
public ClientStore(AppConfigurationProviderProperties appProperties, ConnectionPool pool,
49-
AppConfigurationCredentialProvider tokenCredentialProvider,
50-
ConfigurationClientBuilderSetup clientProvider) {
54+
AppConfigurationCredentialProvider tokenCredentialProvider,
55+
ConfigurationClientBuilderSetup clientProvider) {
5156
this.appProperties = appProperties;
5257
this.pool = pool;
5358
this.tokenCredentialProvider = tokenCredentialProvider;
5459
this.clientProvider = clientProvider;
60+
this.clients = new HashMap<String, ConfigurationAsyncClient>();
5561
}
5662

5763
private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumentException {
64+
if (clients.containsKey(store)) {
65+
return clients.get(store);
66+
}
5867
ConfigurationClientBuilder builder = getBuilder();
5968
ExponentialBackoff retryPolicy = new ExponentialBackoff(appProperties.getMaxRetries(),
60-
Duration.ofMillis(800), Duration.ofSeconds(8));
69+
Duration.ofMillis(800), Duration.ofSeconds(8));
6170
builder = builder.addPolicy(new BaseAppConfigurationPolicy()).retryPolicy(new RetryPolicy(
62-
retryPolicy));
71+
retryPolicy));
6372

6473
TokenCredential tokenCredential = null;
6574
Connection connection = pool.get(store);
@@ -72,26 +81,26 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
7281
tokenCredential = tokenCredentialProvider.getAppConfigCredential(endpoint);
7382
}
7483
if ((tokenCredential != null
75-
|| (connection.getClientId() != null && StringUtils.isNotEmpty(connection.getClientId())))
76-
&& (connection != null && StringUtils.isNotEmpty(connection.getConnectionString()))) {
84+
|| (connection.getClientId() != null && StringUtils.isNotEmpty(connection.getClientId())))
85+
&& (connection != null && StringUtils.isNotEmpty(connection.getConnectionString()))) {
7786
throw new IllegalArgumentException(
78-
"More than 1 Conncetion method was set for connecting to App Configuration.");
87+
"More than 1 Conncetion method was set for connecting to App Configuration.");
7988
} else if (tokenCredential != null && connection != null && connection.getClientId() != null
80-
&& StringUtils.isNotEmpty(connection.getClientId())) {
89+
&& StringUtils.isNotEmpty(connection.getClientId())) {
8190
throw new IllegalArgumentException(
82-
"More than 1 Conncetion method was set for connecting to App Configuration.");
91+
"More than 1 Conncetion method was set for connecting to App Configuration.");
8392
}
8493

8594
if (tokenCredential != null) {
8695
// User Provided Token Credential
8796
LOGGER.debug("Connecting to " + endpoint + " using AppConfigurationCredentialProvider.");
8897
builder.credential(tokenCredential);
8998
} else if ((connection.getClientId() != null && StringUtils.isNotEmpty(connection.getClientId()))
90-
&& connection.getEndpoint() != null) {
99+
&& connection.getEndpoint() != null) {
91100
// User Assigned Identity - Client ID through configuration file.
92101
LOGGER.debug("Connecting to " + endpoint + " using Client ID from configuration file.");
93102
ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder()
94-
.clientId(connection.getClientId());
103+
.clientId(connection.getClientId());
95104
builder.credential(micBuilder.build());
96105
} else if (StringUtils.isNotEmpty(connection.getConnectionString())) {
97106
// Connection String
@@ -101,7 +110,7 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
101110
// System Assigned Identity. Needs to be checked last as all of the above
102111
// should have a Endpoint.
103112
LOGGER.debug("Connecting to " + endpoint
104-
+ " using Azure System Assigned Identity or Azure User Assigned Identity.");
113+
+ " using Azure System Assigned Identity or Azure User Assigned Identity.");
105114
ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder();
106115
builder.credential(micBuilder.build());
107116
} else {
@@ -114,47 +123,75 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
114123
clientProvider.setup(builder, endpoint);
115124
}
116125

117-
return builder.buildAsyncClient();
126+
clients.put(store, builder.buildAsyncClient());
127+
return clients.get(store);
118128
}
119129

120130
/**
121-
* Gets the latest Configuration Setting from the revisions given config store that
122-
* match the Setting Selector criteria.
131+
* Gets the latest Configuration Setting from the revisions given config store that match the Setting Selector
132+
* criteria.
123133
*
124-
* @param settingSelector Information on which setting to pull. i.e. number of
125-
* results, key value...
134+
* @param settingSelector Information on which setting to pull. i.e. number of results, key value...
126135
* @param storeName Name of the App Configuration store to query against.
127136
* @return List of Configuration Settings.
128137
*/
129138
public final ConfigurationSetting getRevison(SettingSelector settingSelector, String storeName) {
139+
PagedResponse<ConfigurationSetting> configurationRevision = null;
140+
int retryCount = 0;
141+
130142
ConfigurationAsyncClient client = buildClient(storeName);
131-
return client.listRevisions(settingSelector).blockFirst();
143+
while (retryCount <= appProperties.getMaxRetries()) {
144+
configurationRevision = client.listRevisions(settingSelector).byPage().blockFirst();
145+
146+
if (configurationRevision != null
147+
&& configurationRevision.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS.value()) {
148+
HttpHeader retryAfterHeader = configurationRevision.getHeaders().get("retry-after-ms");
149+
150+
if (retryAfterHeader != null) {
151+
try {
152+
Integer retryAfter = Integer.valueOf(retryAfterHeader.getValue());
153+
154+
Thread.sleep(retryAfter);
155+
} catch (NumberFormatException e) {
156+
LOGGER.warn("Unable to parse Retry After value.", e);
157+
} catch (InterruptedException e) {
158+
LOGGER.warn("Failed to wait after getting 429.", e);
159+
}
160+
}
161+
162+
configurationRevision = null;
163+
} else if (configurationRevision != null && configurationRevision.getItems().size() > 0) {
164+
return configurationRevision.getItems().get(0);
165+
} else {
166+
return null;
167+
}
168+
retryCount++;
169+
}
170+
return null;
132171
}
133172

134173
/**
135-
* Gets a list of Configuration Settings from the given config store that match the
136-
* Setting Selector criteria.
174+
* Gets a list of Configuration Settings from the given config store that match the Setting Selector criteria.
137175
*
138-
* @param settingSelector Information on which setting to pull. i.e. number of
139-
* results, key value...
176+
* @param settingSelector Information on which setting to pull. i.e. number of results, key value...
140177
* @param storeName Name of the App Configuration store to query against.
141178
* @return List of Configuration Settings.
142179
* @throws IOException thrown when failed to retrieve values.
143180
*/
144181
public final List<ConfigurationSetting> listSettings(SettingSelector settingSelector, String storeName)
145-
throws IOException {
182+
throws IOException {
146183
ConfigurationAsyncClient client = buildClient(storeName);
147184

148185
return client.listConfigurationSettings(settingSelector).collectList().block();
149186
}
150187

151188
/**
152-
* Composite watched key names separated by comma, the key names is made up of:
153-
* prefix, context and key name pattern e.g., prefix: /config, context: /application,
154-
* watched key: my.watch.key will return: /config/application/my.watch.key
189+
* Composite watched key names separated by comma, the key names is made up of: prefix, context and key name pattern
190+
* e.g., prefix: /config, context: /application, watched key: my.watch.key will return:
191+
* /config/application/my.watch.key
155192
*
156-
* The returned watched key will be one key pattern, one or multiple specific keys
157-
* e.g., 1) * 2) /application/abc* 3) /application/abc 4) /application/abc,xyz
193+
* The returned watched key will be one key pattern, one or multiple specific keys e.g., 1) * 2) /application/abc*
194+
* 3) /application/abc 4) /application/abc,xyz
158195
*
159196
* @param store the {@code store} for which to composite watched key names
160197
* @param storeContextsMap map storing store name and List of context key-value pair

0 commit comments

Comments
 (0)