Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.appconfiguration.config.web.implementation.pullrefresh;

import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.ACTUATOR;
import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.APPCONFIGURATION_REFRESH;
import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.APPCONFIGURATION_REFRESH_BUS;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.web.context.support.ServletRequestHandledEvent;

import com.azure.spring.cloud.appconfiguration.config.AppConfigurationRefresh;
import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.ACTUATOR;
import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.APPCONFIGURATION_REFRESH;
import static com.azure.spring.cloud.appconfiguration.config.web.implementation.AppConfigurationWebConstants.APPCONFIGURATION_REFRESH_BUS;

/**
* Listens for ServletRequestHandledEvents to check if the configurations need to be updated.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.data.appconfiguration.models.SnapshotComposition;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;

import io.netty.handler.codec.http.HttpResponseStatus;

Expand Down Expand Up @@ -150,15 +150,47 @@ List<ConfigurationSetting> listSettings(SettingSelector settingSelector, Context
}
}

/**
* Gets configuration settings using collection monitoring. This method retrieves all settings matching
* the selector and captures ETags for collection-based refresh monitoring.
*
* @param settingSelector selector criteria for configuration settings
* @param context Azure SDK context for request correlation
* @return CollectionMonitoring containing the retrieved configuration settings and match conditions
* @throws HttpResponseException if the request fails
*/
WatchedConfigurationSettings collectionMonitoring(SettingSelector settingSelector, Context context) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
WatchedConfigurationSettings collectionMonitoring(SettingSelector settingSelector, Context context) {
WatchedConfigurationSettings loadWatchedSettings(SettingSelector settingSelector, Context context) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or listKeyValues since the next method is listFeatureFlags

List<ConfigurationSetting> configurationSettings = new ArrayList<>();
List<MatchConditions> checks = new ArrayList<>();
try {
client.listConfigurationSettings(settingSelector, context).streamByPage().forEach(pagedResponse -> {
checks.add(
new MatchConditions().setIfNoneMatch(pagedResponse.getHeaders().getValue(HttpHeaderName.ETAG)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to check status code before processing pagedResponse here

for (ConfigurationSetting setting : pagedResponse.getValue()) {
configurationSettings.add(NormalizeNull.normalizeNullLabel(setting));
}
});

// Needs to happen after or we don't know if the request succeeded or failed.
this.failedAttempts = 0;
settingSelector.setMatchConditions(checks);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the response is 304 not modified? Does this override the previous MatchConditions?

return new WatchedConfigurationSettings(settingSelector, configurationSettings);
} catch (HttpResponseException e) {
throw handleHttpResponseException(e);
} catch (UncheckedIOException e) {
throw new AppConfigurationStatusException(e.getMessage(), null, null);
}
}

/**
* Lists feature flags from the Azure App Configuration store.
*
* @param settingSelector selector criteria for feature flags
* @param context Azure SDK context for request correlation
* @return FeatureFlags containing the retrieved feature flags and match conditions
* @return CollectionMonitoring containing the retrieved feature flags and match conditions
* @throws HttpResponseException if the request fails
*/
FeatureFlags listFeatureFlags(SettingSelector settingSelector, Context context)
WatchedConfigurationSettings listFeatureFlags(SettingSelector settingSelector, Context context)
throws HttpResponseException {
List<ConfigurationSetting> configurationSettings = new ArrayList<>();
List<MatchConditions> checks = new ArrayList<>();
Expand All @@ -175,7 +207,7 @@ FeatureFlags listFeatureFlags(SettingSelector settingSelector, Context context)
// Needs to happen after or we don't know if the request succeeded or failed.
this.failedAttempts = 0;
settingSelector.setMatchConditions(checks);
return new FeatureFlags(settingSelector, configurationSettings);
return new WatchedConfigurationSettings(settingSelector, configurationSettings);
} catch (HttpResponseException e) {
throw handleHttpResponseException(e);
} catch (UncheckedIOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;

/**
* Azure App Configuration PropertySource unique per Store Label(Profile) combo.
Expand Down Expand Up @@ -49,7 +49,7 @@ final class AppConfigurationSnapshotPropertySource extends AppConfigurationAppli
public void initProperties(List<String> trim, Context context) throws InvalidConfigurationPropertyValueException {
processConfigurationSettings(replicaClient.listSettingSnapshot(snapshotName, context), null, trim);

FeatureFlags featureFlags = new FeatureFlags(null, featureFlagsList);
WatchedConfigurationSettings featureFlags = new WatchedConfigurationSettings(null, featureFlagsList);
featureFlagClient.proccessFeatureFlags(featureFlags, replicaClient.getEndpoint());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
Expand All @@ -22,7 +23,8 @@

import com.azure.core.util.Context;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationKeyValueSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring;
import com.azure.spring.cloud.appconfiguration.config.implementation.properties.AppConfigurationStoreMonitoring.PushNotification;
Expand Down Expand Up @@ -127,7 +129,7 @@ public ConfigData load(ConfigDataLoaderContext context, AzureAppConfigDataResour
if ((notification.getPrimaryToken() != null
&& StringUtils.hasText(notification.getPrimaryToken().getName()))
|| (notification.getSecondaryToken() != null
&& StringUtils.hasText(notification.getPrimaryToken().getName()))) {
&& StringUtils.hasText(notification.getSecondaryToken().getName()))) {
pushRefresh = true;
}
// Feature Management needs to be set in the last config store.
Expand All @@ -152,7 +154,7 @@ public ConfigData load(ConfigDataLoaderContext context, AzureAppConfigDataResour
// Reverse in order to add Profile specific properties earlier, and last profile comes first
try {
sourceList.addAll(createSettings(currentClient));
List<FeatureFlags> featureFlags = createFeatureFlags(currentClient);
List<WatchedConfigurationSettings> featureFlags = createFeatureFlags(currentClient);

logger.debug("PropertySource context.");
AppConfigurationStoreMonitoring monitoring = resource.getMonitoring();
Expand All @@ -161,13 +163,20 @@ public ConfigData load(ConfigDataLoaderContext context, AzureAppConfigDataResour
monitoring.getFeatureFlagRefreshInterval());

if (monitoring.isEnabled()) {
// Setting new ETag values for Watch
List<ConfigurationSetting> watchKeysSettings = monitoring.getTriggers().stream()
.map(trigger -> currentClient.getWatchKey(trigger.getKey(), trigger.getLabel(),
requestContext))
.toList();

storeState.setState(resource.getEndpoint(), watchKeysSettings, monitoring.getRefreshInterval());
// Check if refreshAll is enabled - if so, use collection monitoring
if (monitoring.getTriggers().size() == 0) {
// Use collection monitoring for refresh
List<WatchedConfigurationSettings> collectionMonitoringList = getWatchedConfigurationSettings(currentClient);
storeState.setState(resource.getEndpoint(), Collections.emptyList(), collectionMonitoringList, monitoring.getRefreshInterval());
} else {
// Use traditional watch key monitoring
List<ConfigurationSetting> watchKeysSettings = monitoring.getTriggers().stream()
.map(trigger -> currentClient.getWatchKey(trigger.getKey(), trigger.getLabel(),
requestContext))
.toList();

storeState.setState(resource.getEndpoint(), watchKeysSettings, monitoring.getRefreshInterval());
}
}
storeState.setLoadState(resource.getEndpoint(), true); // Success - configuration loaded, exit loop
lastException = null;
Expand Down Expand Up @@ -259,23 +268,57 @@ private List<AppConfigurationPropertySource> createSettings(AppConfigurationRepl
* Creates a list of feature flags from Azure App Configuration.
*
* @param client client for connecting to App Configuration
* @return a list of FeatureFlags
* @return a list of CollectionMonitoring
* @throws Exception creating feature flags failed
*/
private List<FeatureFlags> createFeatureFlags(AppConfigurationReplicaClient client)
private List<WatchedConfigurationSettings> createFeatureFlags(AppConfigurationReplicaClient client)
throws Exception {
List<FeatureFlags> featureFlagWatchKeys = new ArrayList<>();
List<WatchedConfigurationSettings> featureFlagWatchKeys = new ArrayList<>();
List<String> profiles = resource.getProfiles().getActive();

for (FeatureFlagKeyValueSelector selectedKeys : resource.getFeatureFlagSelects()) {
List<FeatureFlags> storesFeatureFlags = featureFlagClient.loadFeatureFlags(client,
List<WatchedConfigurationSettings> storesFeatureFlags = featureFlagClient.loadFeatureFlags(client,
selectedKeys.getKeyFilter(), selectedKeys.getLabelFilter(profiles), requestContext);
featureFlagWatchKeys.addAll(storesFeatureFlags);
}

return featureFlagWatchKeys;
}

/**
* Creates a list of collection monitoring for configuration settings from Azure App Configuration.
* This is used for collection-based refresh monitoring as an alternative to individual watch keys.
*
* @param client client for connecting to App Configuration
* @return a list of CollectionMonitoring for configuration settings
* @throws Exception creating collection monitoring failed
*/
private List<WatchedConfigurationSettings> getWatchedConfigurationSettings(AppConfigurationReplicaClient client)
throws Exception {
List<WatchedConfigurationSettings> collectionMonitoringList = new ArrayList<>();
List<AppConfigurationKeyValueSelector> selects = resource.getSelects();
List<String> profiles = resource.getProfiles().getActive();

for (AppConfigurationKeyValueSelector selectedKeys : selects) {
// Skip snapshots - they don't support collection monitoring
if (StringUtils.hasText(selectedKeys.getSnapshotName())) {
continue;
}

// Create collection monitoring for each label
for (String label : selectedKeys.getLabelFilter(profiles)) {
SettingSelector settingSelector = new SettingSelector()
.setKeyFilter(selectedKeys.getKeyFilter() + "*")
.setLabelFilter(label);

WatchedConfigurationSettings collectionMonitoring = client.collectionMonitoring(settingSelector, requestContext);
collectionMonitoringList.add(collectionMonitoring);
}
}

return collectionMonitoringList;
}

/**
* Logs a replica failure with contextual information about the failure scenario and available replicas.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagFilter;
import com.azure.data.appconfiguration.models.SettingSelector;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.FeatureFlags;
import com.azure.spring.cloud.appconfiguration.config.implementation.configuration.WatchedConfigurationSettings;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity.Allocation;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity.Feature;
import com.azure.spring.cloud.appconfiguration.config.implementation.feature.entity.FeatureTelemetry;
Expand Down Expand Up @@ -78,9 +78,9 @@ class FeatureFlagClient {
* </p>
*
*/
List<FeatureFlags> loadFeatureFlags(AppConfigurationReplicaClient replicaClient, String customKeyFilter,
List<WatchedConfigurationSettings> loadFeatureFlags(AppConfigurationReplicaClient replicaClient, String customKeyFilter,
String[] labelFilter, Context context) {
List<FeatureFlags> loadedFeatureFlags = new ArrayList<>();
List<WatchedConfigurationSettings> loadedFeatureFlags = new ArrayList<>();

String keyFilter = SELECT_ALL_FEATURE_FLAGS;

Expand All @@ -95,26 +95,23 @@ List<FeatureFlags> loadFeatureFlags(AppConfigurationReplicaClient replicaClient,
SettingSelector settingSelector = new SettingSelector().setKeyFilter(keyFilter).setLabelFilter(label);
context.addData("FeatureFlagTracing", tracing);

FeatureFlags features = replicaClient.listFeatureFlags(settingSelector, context);
loadedFeatureFlags.addAll(proccessFeatureFlags(features, replicaClient.getOriginClient()));
WatchedConfigurationSettings features = replicaClient.listFeatureFlags(settingSelector, context);
loadedFeatureFlags.add(proccessFeatureFlags(features, replicaClient.getOriginClient()));
}
return loadedFeatureFlags;
}

List<FeatureFlags> proccessFeatureFlags(FeatureFlags features, String endpoint) {
List<FeatureFlags> loadedFeatureFlags = new ArrayList<>();
loadedFeatureFlags.add(features);

WatchedConfigurationSettings proccessFeatureFlags(WatchedConfigurationSettings features, String endpoint) {
// Reading In Features
for (ConfigurationSetting setting : features.getFeatureFlags()) {
for (ConfigurationSetting setting : features.getConfigurationSettings()) {
if (setting instanceof FeatureFlagConfigurationSetting
&& FEATURE_FLAG_CONTENT_TYPE.equals(setting.getContentType())) {
FeatureFlagConfigurationSetting featureFlag = (FeatureFlagConfigurationSetting) setting;
updateTelemetry(featureFlag);
properties.put(featureFlag.getKey(), createFeature(featureFlag, endpoint));
}
}
return loadedFeatureFlags;
return features;
}

/**
Expand Down
Loading
Loading