Skip to content

Commit a4de2bc

Browse files
authored
Push Refresh Telemetry (#44170)
* Updating to use Context to pass around telemetry + added pushRefreshEnabled * Updated to PUSH_REFRESH + tests
1 parent d3750bf commit a4de2bc

17 files changed

+306
-142
lines changed

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationApplicationSettingPropertySource.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
1919
import org.springframework.util.StringUtils;
2020

21+
import com.azure.core.util.Context;
2122
import com.azure.data.appconfiguration.models.ConfigurationSetting;
2223
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
2324
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
@@ -60,7 +61,7 @@ class AppConfigurationApplicationSettingPropertySource extends AppConfigurationP
6061
* @param keyPrefixTrimValues prefixs to trim from key values
6162
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
6263
*/
63-
public void initProperties(List<String> keyPrefixTrimValues, boolean isRefresh) throws InvalidConfigurationPropertyValueException {
64+
public void initProperties(List<String> keyPrefixTrimValues, Context context) throws InvalidConfigurationPropertyValueException {
6465

6566
List<String> labels = Arrays.asList(labelFilters);
6667
// Reverse labels so they have the right priority order.
@@ -70,7 +71,7 @@ public void initProperties(List<String> keyPrefixTrimValues, boolean isRefresh)
7071
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter + "*").setLabelFilter(label);
7172

7273
// * for wildcard match
73-
processConfigurationSettings(replicaClient.listSettings(settingSelector, isRefresh), settingSelector.getKeyFilter(),
74+
processConfigurationSettings(replicaClient.listSettings(settingSelector, context), settingSelector.getKeyFilter(),
7475
keyPrefixTrimValues);
7576
}
7677
}

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationConstants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public class AppConfigurationConstants {
3636
* Constant for tracing if Key Vault is configured for use.
3737
*/
3838
public static final String KEY_VAULT_CONFIGURED_TRACING = "UsesKeyVault";
39+
40+
/**
41+
* Constant for tracing if Push Refresh is enabled for the store.
42+
*/
43+
public static final String PUSH_REFRESH = "PushRefresh";
3944

4045
/**
4146
* Http Header User Agent

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPropertySource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
1111
import org.springframework.core.env.EnumerablePropertySource;
1212

13+
import com.azure.core.util.Context;
1314
import com.azure.data.appconfiguration.ConfigurationClient;
1415

1516
/**
@@ -51,5 +52,5 @@ protected static String getLabelName(String[] labelFilters) {
5152
return String.join(",", labelFilters);
5253
}
5354

54-
protected abstract void initProperties(List<String> trim, boolean isRefresh) throws InvalidConfigurationPropertyValueException;
55+
protected abstract void initProperties(List<String> trim, Context context) throws InvalidConfigurationPropertyValueException;
5556
}

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22
// Licensed under the MIT License.
33
package com.azure.spring.cloud.appconfiguration.config.implementation;
44

5+
import static com.azure.spring.cloud.appconfiguration.config.implementation.AppConfigurationConstants.PUSH_REFRESH;
56
import java.time.Duration;
67
import java.time.Instant;
78
import java.util.List;
89
import java.util.Map.Entry;
910

1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
13+
import org.springframework.util.StringUtils;
1214

1315
import com.azure.core.exception.HttpResponseException;
16+
import com.azure.core.util.Context;
1417
import com.azure.data.appconfiguration.models.ConfigurationSetting;
1518
import com.azure.spring.cloud.appconfiguration.config.implementation.autofailover.ReplicaLookUp;
1619
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlagState;
1720
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
1821
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
22+
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.PushNotification;
1923
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.FeatureFlagStore;
2024

2125
public class AppConfigurationRefreshUtil {
@@ -50,14 +54,22 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
5054
clientFactory.setCurrentConfigStoreClient(originEndpoint, originEndpoint);
5155

5256
AppConfigurationStoreMonitoring monitor = connection.getMonitoring();
57+
58+
boolean pushRefresh = false;
59+
PushNotification notification = monitor.getPushNotification();
60+
if ((notification.getPrimaryToken() != null && StringUtils.hasText(notification.getPrimaryToken().getName()))
61+
|| (notification.getSecondaryToken() != null && StringUtils.hasText(notification.getPrimaryToken().getName()))) {
62+
pushRefresh = true;
63+
}
64+
Context context = new Context("refresh", true).addData(PUSH_REFRESH, pushRefresh);
5365

5466
List<AppConfigurationReplicaClient> clients = clientFactory.getAvailableClients(originEndpoint);
5567

5668
if (monitor.isEnabled() && StateHolder.getLoadState(originEndpoint)) {
5769
for (AppConfigurationReplicaClient client : clients) {
5870
try {
5971
refreshWithTime(client, StateHolder.getState(originEndpoint), monitor.getRefreshInterval(),
60-
eventData, replicaLookUp);
72+
eventData, replicaLookUp, context);
6173
if (eventData.getDoRefresh()) {
6274
clientFactory.setCurrentConfigStoreClient(originEndpoint, client.getEndpoint());
6375
return eventData;
@@ -81,7 +93,7 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
8193
for (AppConfigurationReplicaClient client : clients) {
8294
try {
8395
refreshWithTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(originEndpoint),
84-
monitor.getFeatureFlagRefreshInterval(), eventData, replicaLookUp);
96+
monitor.getFeatureFlagRefreshInterval(), eventData, replicaLookUp, context);
8597
if (eventData.getDoRefresh()) {
8698
clientFactory.setCurrentConfigStoreClient(originEndpoint, client.getEndpoint());
8799
return eventData;
@@ -115,10 +127,10 @@ RefreshEventData refreshStoresCheck(AppConfigurationReplicaClientFactory clientF
115127
* @param originEndpoint config store origin endpoint
116128
* @return A refresh should be triggered.
117129
*/
118-
static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String originEndpoint) {
130+
static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String originEndpoint, Context context) {
119131
RefreshEventData eventData = new RefreshEventData();
120132
if (StateHolder.getLoadState(originEndpoint)) {
121-
refreshWithoutTime(client, StateHolder.getState(originEndpoint).getWatchKeys(), eventData);
133+
refreshWithoutTime(client, StateHolder.getState(originEndpoint).getWatchKeys(), eventData, context);
122134
}
123135
return eventData.getDoRefresh();
124136
}
@@ -131,12 +143,12 @@ static boolean refreshStoreCheck(AppConfigurationReplicaClient client, String or
131143
* @return true if a refresh should be triggered.
132144
*/
133145
static boolean refreshStoreFeatureFlagCheck(Boolean featureStoreEnabled,
134-
AppConfigurationReplicaClient client) {
146+
AppConfigurationReplicaClient client, Context context) {
135147
RefreshEventData eventData = new RefreshEventData();
136148
String endpoint = client.getEndpoint();
137149

138150
if (featureStoreEnabled && StateHolder.getStateFeatureFlag(endpoint) != null) {
139-
refreshWithoutTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(endpoint), eventData);
151+
refreshWithoutTimeFeatureFlags(client, StateHolder.getStateFeatureFlag(endpoint), eventData, context);
140152
} else {
141153
LOGGER.debug("Skipping feature flag refresh check for " + endpoint);
142154
}
@@ -151,10 +163,10 @@ static boolean refreshStoreFeatureFlagCheck(Boolean featureStoreEnabled,
151163
* @param eventData Info for this refresh event.
152164
*/
153165
private static void refreshWithTime(AppConfigurationReplicaClient client, State state, Duration refreshInterval,
154-
RefreshEventData eventData, ReplicaLookUp replicaLookUp) throws AppConfigurationStatusException {
166+
RefreshEventData eventData, ReplicaLookUp replicaLookUp, Context context) throws AppConfigurationStatusException {
155167
if (Instant.now().isAfter(state.getNextRefreshCheck())) {
156168
replicaLookUp.updateAutoFailoverEndpoints();
157-
refreshWithoutTime(client, state.getWatchKeys(), eventData);
169+
refreshWithoutTime(client, state.getWatchKeys(), eventData, context);
158170

159171
StateHolder.getCurrentState().updateStateRefresh(state, refreshInterval);
160172
}
@@ -168,9 +180,9 @@ private static void refreshWithTime(AppConfigurationReplicaClient client, State
168180
* @param eventData Refresh event info
169181
*/
170182
private static void refreshWithoutTime(AppConfigurationReplicaClient client, List<ConfigurationSetting> watchKeys,
171-
RefreshEventData eventData) throws AppConfigurationStatusException {
183+
RefreshEventData eventData, Context context) throws AppConfigurationStatusException {
172184
for (ConfigurationSetting watchKey : watchKeys) {
173-
ConfigurationSetting watchedKey = client.getWatchKey(watchKey.getKey(), watchKey.getLabel(), true);
185+
ConfigurationSetting watchedKey = client.getWatchKey(watchKey.getKey(), watchKey.getLabel(), context);
174186

175187
// If there is no result, etag will be considered empty.
176188
// A refresh will trigger once the selector returns a value.
@@ -184,14 +196,14 @@ private static void refreshWithoutTime(AppConfigurationReplicaClient client, Lis
184196
}
185197

186198
private static void refreshWithTimeFeatureFlags(AppConfigurationReplicaClient client, FeatureFlagState state,
187-
Duration refreshInterval, RefreshEventData eventData, ReplicaLookUp replicaLookUp)
199+
Duration refreshInterval, RefreshEventData eventData, ReplicaLookUp replicaLookUp, Context context)
188200
throws AppConfigurationStatusException {
189201
Instant date = Instant.now();
190202
if (date.isAfter(state.getNextRefreshCheck())) {
191203
replicaLookUp.updateAutoFailoverEndpoints();
192204

193205
for (FeatureFlags featureFlags : state.getWatchKeys()) {
194-
if (client.checkWatchKeys(featureFlags.getSettingSelector(), true)) {
206+
if (client.checkWatchKeys(featureFlags.getSettingSelector(), context)) {
195207
String eventDataInfo = ".appconfig.featureflag/*";
196208

197209
// Only one refresh Event needs to be call to update all of the
@@ -209,10 +221,10 @@ private static void refreshWithTimeFeatureFlags(AppConfigurationReplicaClient cl
209221
}
210222

211223
private static void refreshWithoutTimeFeatureFlags(AppConfigurationReplicaClient client, FeatureFlagState watchKeys,
212-
RefreshEventData eventData) throws AppConfigurationStatusException {
224+
RefreshEventData eventData, Context context) throws AppConfigurationStatusException {
213225

214226
for (FeatureFlags featureFlags : watchKeys.getWatchKeys()) {
215-
if (client.checkWatchKeys(featureFlags.getSettingSelector(), true)) {
227+
if (client.checkWatchKeys(featureFlags.getSettingSelector(), context)) {
216228
String eventDataInfo = ".appconfig.featureflag/*";
217229

218230
// Only one refresh Event needs to be call to update all of the

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationReplicaClient.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,9 @@ String getEndpoint() {
8888
* @param label String value of the watch key, use \0 for null.
8989
* @return The first returned configuration.
9090
*/
91-
ConfigurationSetting getWatchKey(String key, String label, boolean isRefresh)
91+
ConfigurationSetting getWatchKey(String key, String label, Context context)
9292
throws HttpResponseException {
9393
try {
94-
Context context = new Context("refresh", isRefresh);
9594
ConfigurationSetting selector = new ConfigurationSetting().setKey(key).setLabel(label);
9695
ConfigurationSetting watchKey = NormalizeNull
9796
.normalizeNullLabel(
@@ -111,11 +110,10 @@ ConfigurationSetting getWatchKey(String key, String label, boolean isRefresh)
111110
* @param settingSelector Information on which setting to pull. i.e. number of results, key value...
112111
* @return List of Configuration Settings.
113112
*/
114-
List<ConfigurationSetting> listSettings(SettingSelector settingSelector, boolean isRefresh)
113+
List<ConfigurationSetting> listSettings(SettingSelector settingSelector, Context context)
115114
throws HttpResponseException {
116115
List<ConfigurationSetting> configurationSettings = new ArrayList<>();
117116
try {
118-
Context context = new Context("refresh", isRefresh);
119117
PagedIterable<ConfigurationSetting> settings = client.listConfigurationSettings(settingSelector, context);
120118
settings.forEach(setting -> {
121119
configurationSettings.add(NormalizeNull.normalizeNullLabel(setting));
@@ -130,11 +128,11 @@ List<ConfigurationSetting> listSettings(SettingSelector settingSelector, boolean
130128
}
131129
}
132130

133-
FeatureFlags listFeatureFlags(SettingSelector settingSelector, boolean isRefresh) throws HttpResponseException {
131+
FeatureFlags listFeatureFlags(SettingSelector settingSelector, Context context)
132+
throws HttpResponseException {
134133
List<ConfigurationSetting> configurationSettings = new ArrayList<>();
135134
List<MatchConditions> checks = new ArrayList<>();
136135
try {
137-
Context context = new Context("refresh", isRefresh);
138136
client.listConfigurationSettings(settingSelector, context).streamByPage().forEach(pagedResponse -> {
139137
checks.add(
140138
new MatchConditions().setIfNoneMatch(pagedResponse.getHeaders().getValue(HttpHeaderName.ETAG)));
@@ -155,12 +153,11 @@ FeatureFlags listFeatureFlags(SettingSelector settingSelector, boolean isRefresh
155153
}
156154
}
157155

158-
List<ConfigurationSetting> listSettingSnapshot(String snapshotName, boolean isRefresh) {
156+
List<ConfigurationSetting> listSettingSnapshot(String snapshotName, Context context) {
159157
List<ConfigurationSetting> configurationSettings = new ArrayList<>();
160158
try {
161159
// Because Spring always refreshes all we still have to load snapshots on refresh to build the property
162160
// sources.
163-
Context context = new Context("refresh", isRefresh);
164161
ConfigurationSnapshot snapshot = client.getSnapshotWithResponse(snapshotName, null, context).getValue();
165162
if (!SnapshotComposition.KEY.equals(snapshot.getSnapshotComposition())) {
166163
throw new IllegalArgumentException("Snapshot " + snapshotName + " needs to be of type Key.");
@@ -177,8 +174,7 @@ List<ConfigurationSetting> listSettingSnapshot(String snapshotName, boolean isRe
177174
}
178175
}
179176

180-
boolean checkWatchKeys(SettingSelector settingSelector, boolean isRefresh) {
181-
Context context = new Context("refresh", false);
177+
boolean checkWatchKeys(SettingSelector settingSelector, Context context) {
182178
List<PagedResponse<ConfigurationSetting>> results = client.listConfigurationSettings(settingSelector, context)
183179
.streamByPage().filter(pagedResponse -> pagedResponse.getStatusCode() != 304).toList();
184180
return results.size() > 0;

sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationSnapshotPropertySource.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
99

10+
import com.azure.core.util.Context;
1011
import com.azure.data.appconfiguration.models.ConfigurationSetting;
1112
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
1213
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
@@ -45,8 +46,8 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli
4546
* @param isRefresh true if a refresh triggered the loading of the Snapshot.
4647
* @throws InvalidConfigurationPropertyValueException thrown if fails to parse Json content type
4748
*/
48-
public void initProperties(List<String> trim, boolean isRefresh) throws InvalidConfigurationPropertyValueException {
49-
processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, isRefresh), null, trim);
49+
public void initProperties(List<String> trim, Context context) throws InvalidConfigurationPropertyValueException {
50+
processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, context), null, trim);
5051

5152
FeatureFlags featureFlags = new FeatureFlags(null, featureFlagsList);
5253
featureFlagClient.proccessFeatureFlags(featureFlags, replicaClient.getEndpoint());

0 commit comments

Comments
 (0)