Skip to content
Merged
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

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 watched configuration settings. 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 WatchedConfigurationSettings containing the retrieved configuration settings and match conditions
* @throws HttpResponseException if the request fails
*/
WatchedConfigurationSettings loadWatchedSettings(SettingSelector settingSelector, Context context) {
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)));
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);
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 WatchedConfigurationSettings 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,11 +7,12 @@
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;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.context.config.ConfigDataLoader;
import org.springframework.boot.context.config.ConfigDataLoaderContext;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
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,23 @@ 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 watched configuration settings
if (monitoring.getTriggers().size() == 0) {
// Use watched configuration settings for refresh
List<WatchedConfigurationSettings> watchedConfigurationSettingsList = getWatchedConfigurationSettings(
currentClient);
storeState.setState(resource.getEndpoint(), Collections.emptyList(),
watchedConfigurationSettingsList, 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 @@ -260,23 +272,58 @@ 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 WatchedConfigurationSettings
* @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 watched configuration settings 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 WatchedConfigurationSettings for configuration settings
* @throws Exception creating watched configuration settings failed
*/
private List<WatchedConfigurationSettings> getWatchedConfigurationSettings(AppConfigurationReplicaClient client)
throws Exception {
List<WatchedConfigurationSettings> watchedConfigurationSettingsList = new ArrayList<>();
List<AppConfigurationKeyValueSelector> selects = resource.getSelects();
List<String> profiles = resource.getProfiles().getActive();

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

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

WatchedConfigurationSettings watchedConfigurationSettings = client.loadWatchedSettings(settingSelector,
requestContext);
watchedConfigurationSettingsList.add(watchedConfigurationSettings);
}
}

return watchedConfigurationSettingsList;
}

/**
* 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