From e9bd31997c5c04aa4ba1c8d77871a6e361b6fbff Mon Sep 17 00:00:00 2001 From: mrm9084 Date: Fri, 10 Jan 2020 16:27:14 -0800 Subject: [PATCH] README update + 2 bug fixes --- .../cloud/config/AzureCloudConfigRefresh.java | 15 ++- .../config/AzureConfigPropertySource.java | 3 +- .../config/AzureConfigCloudRefreshTest.java | 54 +++++---- .../config/AzureConfigPropertySourceTest.java | 14 ++- .../FeatureManagementConfiguration.java | 8 +- .../cloud/feature/manager/FeatureManager.java | 98 ++++++++++++++-- .../feature/manager/entities/FeatureSet.java | 105 ------------------ .../feature/manager/FeatureManagerTest.java | 55 +++------ .../cloud/feature/manager/FeatureSetTest.java | 48 -------- .../README.md | 20 ++-- 10 files changed, 168 insertions(+), 252 deletions(-) delete mode 100644 spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/entities/FeatureSet.java delete mode 100644 spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureSetTest.java diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureCloudConfigRefresh.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureCloudConfigRefresh.java index 0c8e1e718..493ec775e 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureCloudConfigRefresh.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureCloudConfigRefresh.java @@ -141,18 +141,23 @@ private boolean refresh(ConfigStore store, String storeSuffix, String watchedKey if (items != null && !items.isEmpty()) { etag = items.get(0).getETag(); } - + if (StateHolder.getState(storeNameWithSuffix) == null) { + // Should never be the case as Property Source should set the state, but if + // etag != null return true. + if (etag != null) { + return true; + } return false; } - + if (!etag.equals(StateHolder.getState(storeNameWithSuffix).getETag())) { LOGGER.trace("Some keys in store [{}] matching [{}] is updated, will send refresh event.", store.getEndpoint(), watchedKeyNames); - if (eventDataInfo.isEmpty()) { - eventDataInfo = watchedKeyNames; + if (this.eventDataInfo.isEmpty()) { + this.eventDataInfo = watchedKeyNames; } else { - eventDataInfo += ", " + watchedKeyNames; + this.eventDataInfo += ", " + watchedKeyNames; } // Don't need to refresh here will be done in Property Source diff --git a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySource.java b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySource.java index ce3ed211a..5791576a9 100644 --- a/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySource.java +++ b/spring-cloud-azure-appconfiguration-config/src/main/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySource.java @@ -238,7 +238,8 @@ private String getKeyVaultEntry(String value) { * @param featureSet Feature Flag info to be set to this property source. */ void initFeatures(FeatureSet featureSet) { - properties.put(FEATURE_MANAGEMENT_KEY, mapper.convertValue(featureSet, LinkedHashMap.class)); + properties.put(FEATURE_MANAGEMENT_KEY, + mapper.convertValue(featureSet.getFeatureManagement(), LinkedHashMap.class)); } /** diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigCloudRefreshTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigCloudRefreshTest.java index 2c6018dee..d1827f8ef 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigCloudRefreshTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigCloudRefreshTest.java @@ -6,6 +6,7 @@ package com.microsoft.azure.spring.cloud.config; import static com.microsoft.azure.spring.cloud.config.Constants.CONFIGURATION_SUFFIX; +import static com.microsoft.azure.spring.cloud.config.Constants.FEATURE_SUFFIX; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_CONN_STRING; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_ETAG; import static com.microsoft.azure.spring.cloud.config.TestConstants.TEST_STORE_NAME; @@ -25,13 +26,13 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.powermock.api.mockito.PowerMockito; import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.cloud.endpoint.event.RefreshEvent; import org.springframework.context.ApplicationEventPublisher; @@ -61,6 +62,8 @@ public class AzureConfigCloudRefreshTest { @Mock private ClientStore clientStoreMock; + + private static final String WATCHED_KEYS = "/application/*"; @Before public void setup() { @@ -69,12 +72,12 @@ public void setup() { ConfigStore store = new ConfigStore(); store.setEndpoint(TEST_STORE_NAME); store.setConnectionString(TEST_CONN_STRING); - store.setWatchedKey("/application/*"); - + store.setWatchedKey(WATCHED_KEYS); + properties = new AzureCloudConfigProperties(); properties.setStores(Arrays.asList(store)); - properties.setCacheExpiration(Duration.ofSeconds(-60)); + properties.setCacheExpiration(Duration.ofMinutes(-60)); contextsMap = new ConcurrentHashMap<>(); contextsMap.put(TEST_STORE_NAME, Arrays.asList(TEST_ETAG)); @@ -88,39 +91,50 @@ public void setup() { item.setKey("fake-etag/application/test.key"); item.setETag("fake-etag"); + when(clientStoreMock.watchedKeyNames(Mockito.any(), Mockito.any())).thenReturn(WATCHED_KEYS); + configRefresh = new AzureCloudConfigRefresh(properties, contextsMap, clientStoreMock); } + @After + public void cleanupMethod() { + StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, new ConfigurationSetting()); + StateHolder.setState(TEST_STORE_NAME + FEATURE_SUFFIX, new ConfigurationSetting()); + } + @Test - public void firstCallShouldPublishEvent() throws Exception { - PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(date); + public void nonUpdatedEtagShouldntPublishEvent() throws Exception { + StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); + StateHolder.setState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + configRefresh.setApplicationEventPublisher(eventPublisher); when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); - when(date.after(Mockito.any(Date.class))).thenReturn(true); - configRefresh.refreshConfigurations(); + configRefresh.refreshConfigurations().get(); verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); } @Test public void updatedEtagShouldPublishEvent() throws Exception { - PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(date); - when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()) - .thenReturn(updatedResponse()); + StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); + StateHolder.setState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); + + when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(initialResponse()); configRefresh.setApplicationEventPublisher(eventPublisher); - when(date.after(Mockito.any(Date.class))).thenReturn(true); - // The first time an action happens it can't update assertFalse(configRefresh.refreshConfigurations().get()); verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); - StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, new ConfigurationSetting()); + when(clientStoreMock.listSettingRevisons(Mockito.any(), Mockito.anyString())).thenReturn(updatedResponse()); // If there is a change it should update assertTrue(configRefresh.refreshConfigurations().get()); verify(eventPublisher, times(1)).publishEvent(any(RefreshEvent.class)); + + StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, updatedResponse().get(0)); + StateHolder.setState(TEST_STORE_NAME + FEATURE_SUFFIX, updatedResponse().get(0)); HashMap map = new HashMap(); map.put("store1_configuration", "fake-etag-updated"); @@ -138,14 +152,14 @@ public void updatedEtagShouldPublishEvent() throws Exception { @Test public void notRefreshTime() throws Exception { - properties.setCacheExpiration(Duration.ofSeconds(60)); - AzureCloudConfigRefresh watchLargeDelay = new AzureCloudConfigRefresh(properties, contextsMap, clientStoreMock); + StateHolder.setState(TEST_STORE_NAME + CONFIGURATION_SUFFIX, initialResponse().get(0)); + StateHolder.setState(TEST_STORE_NAME + FEATURE_SUFFIX, initialResponse().get(0)); - PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(date); - watchLargeDelay.setApplicationEventPublisher(eventPublisher); + properties.setCacheExpiration(Duration.ofMinutes(60)); + AzureCloudConfigRefresh watchLargeDelay = new AzureCloudConfigRefresh(properties, contextsMap, clientStoreMock); - when(date.after(Mockito.any(Date.class))).thenReturn(true); - watchLargeDelay.refreshConfigurations(); + watchLargeDelay.setApplicationEventPublisher(eventPublisher); + watchLargeDelay.refreshConfigurations().get(); // The first time an action happens it can update verify(eventPublisher, times(0)).publishEvent(any(RefreshEvent.class)); diff --git a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySourceTest.java b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySourceTest.java index dc58e73a2..c5cfc0c6d 100644 --- a/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySourceTest.java +++ b/spring-cloud-azure-appconfiguration-config/src/test/java/com/microsoft/azure/spring/cloud/config/AzureConfigPropertySourceTest.java @@ -61,7 +61,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; - public class AzureConfigPropertySourceTest { private static final String EMPTY_CONTENT_TYPE = ""; @@ -86,7 +85,7 @@ public class AzureConfigPropertySourceTest { private static final ConfigurationSetting featureItem = createItem(".appconfig.featureflag/", "Alpha", FEATURE_VALUE, FEATURE_LABEL, FEATURE_FLAG_CONTENT_TYPE); - + private static final ConfigurationSetting featureItem2 = createItem(".appconfig.featureflag/", "Beta", FEATURE_BOOLEAN_VALUE, FEATURE_LABEL, FEATURE_FLAG_CONTENT_TYPE); @@ -103,6 +102,7 @@ public class AzureConfigPropertySourceTest { private static ObjectMapper mapper = new ObjectMapper(); private AzureCloudConfigProperties azureProperties; + @Mock private ClientStore clientStoreMock; @@ -129,9 +129,9 @@ public class AzureConfigPropertySourceTest { @Rule public ExpectedException expected = ExpectedException.none(); - + private AppConfigProviderProperties appProperties; - + private KeyVaultCredentialProvider tokenCredentialProvider = null; @BeforeClass @@ -242,7 +242,8 @@ public void testFeatureFlagCanBeInitedAndQueried() throws IOException { feature.setEnabledFor(filters); featureSetExpected.addFeature("Alpha", feature); featureSetExpected.addFeature("Beta", true); - LinkedHashMap convertedValue = mapper.convertValue(featureSetExpected, LinkedHashMap.class); + LinkedHashMap convertedValue = mapper.convertValue(featureSetExpected.getFeatureManagement(), + LinkedHashMap.class); assertEquals(convertedValue, propertySource.getProperty(FEATURE_MANAGEMENT_KEY)); } @@ -279,7 +280,8 @@ public void testFeatureFlagBuildError() throws IOException { feature.setEnabledFor(filters); featureSetExpected.addFeature("Alpha", feature); featureSetExpected.addFeature("Beta", true); - LinkedHashMap convertedValue = mapper.convertValue(featureSetExpected, LinkedHashMap.class); + LinkedHashMap convertedValue = mapper.convertValue(featureSetExpected.getFeatureManagement(), + LinkedHashMap.class); assertEquals(convertedValue, propertySource.getProperty(FEATURE_MANAGEMENT_KEY)); } diff --git a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagementConfiguration.java b/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagementConfiguration.java index b679b6c35..8259b07b4 100644 --- a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagementConfiguration.java +++ b/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagementConfiguration.java @@ -9,15 +9,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureSet; - @Configuration -@EnableConfigurationProperties({ FeatureManagementConfigProperties.class, FeatureSet.class }) +@EnableConfigurationProperties({ FeatureManagementConfigProperties.class }) public class FeatureManagementConfiguration { @Bean - public FeatureManager featureManager(FeatureManagementConfigProperties properties, FeatureSet featureSet) { - return new FeatureManager(properties, featureSet); + public FeatureManager featureManager(FeatureManagementConfigProperties properties) { + return new FeatureManager(properties); } } diff --git a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManager.java b/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManager.java index bfd4ad29b..3b19f84d5 100644 --- a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManager.java +++ b/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManager.java @@ -5,17 +5,22 @@ */ package com.microsoft.azure.spring.cloud.feature.manager; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; +import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.azure.spring.cloud.feature.manager.entities.Feature; import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureFilterEvaluationContext; -import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureSet; import reactor.core.publisher.Mono; @@ -23,21 +28,28 @@ * Holds information on Feature Management properties and can check if a given feature is * enabled. */ -@Component("FeatureManager") -public class FeatureManager { +@SuppressWarnings("serial") +@Component("FeatureManagement") +@ConfigurationProperties(prefix = "feature-management") +public class FeatureManager extends HashMap { private static final Logger LOGGER = LoggerFactory.getLogger(FeatureManager.class); - private FeatureSet featureManagement; - @Autowired private ApplicationContext context; private FeatureManagementConfigProperties properties; - public FeatureManager(FeatureManagementConfigProperties properties, FeatureSet featureManagement) { + private HashMap featureManagement; + + private HashMap onOff; + + private ObjectMapper mapper = new ObjectMapper(); + + public FeatureManager(FeatureManagementConfigProperties properties) { this.properties = properties; - this.featureManagement = featureManagement; + featureManagement = new HashMap(); + onOff = new HashMap(); } /** @@ -55,13 +67,12 @@ public Mono isEnabledAsync(String feature) { private boolean checkFeatures(String feature) { boolean enabled = false; - if (featureManagement == null || featureManagement.getFeatureManagement() == null || - featureManagement.getOnOff() == null) { + if (featureManagement == null || onOff == null) { return false; } - Feature featureItem = featureManagement.getFeatureManagement().get(feature); - Boolean boolFeature = featureManagement.getOnOff().get(feature); + Feature featureItem = featureManagement.get(feature); + Boolean boolFeature = onOff.get(feature); if (boolFeature != null) { return boolFeature; @@ -89,4 +100,69 @@ private boolean checkFeatures(String feature) { } return enabled; } + + @SuppressWarnings("unchecked") + private void addToFeatures(Map features, String key, String combined) { + Object featureKey = features.get(key); + if (!combined.isEmpty() && !combined.endsWith(".")) { + combined += "."; + } + if (featureKey instanceof Boolean) { + onOff.put(combined + key, (Boolean) featureKey); + } else { + Feature feature = null; + try { + feature = mapper.convertValue(featureKey, Feature.class); + } catch (IllegalArgumentException e) { + LOGGER.error("Found invalid feature {} with value {}.", combined + key, featureKey.toString()); + } + + // When coming from a file "feature.flag" is not a possible flag name + if (feature != null && feature.getEnabledFor() == null && feature.getKey() == null) { + if (LinkedHashMap.class.isAssignableFrom(featureKey.getClass())) { + features = (LinkedHashMap) featureKey; + for (String fKey : features.keySet()) { + addToFeatures(features, fKey, combined + key); + } + } + } else { + if (feature != null) { + feature.setKey(key); + featureManagement.put(key, feature); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void putAll(Map m) { + if (m == null) { + return; + } + + if (m.size() == 1 && m.get("featureManagement") != null) { + m = (Map) m.get("featureManagement"); + } + + for (String key : m.keySet()) { + addToFeatures(m, key, ""); + } + } + + /** + * @return the featureManagement + */ + HashMap getFeatureManagement() { + return featureManagement; + } + + /** + * @return the onOff + */ + HashMap getOnOff() { + return onOff; + } + + } diff --git a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/entities/FeatureSet.java b/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/entities/FeatureSet.java deleted file mode 100644 index 3afe6cf4c..000000000 --- a/spring-cloud-azure-feature-management/src/main/java/com/microsoft/azure/spring/cloud/feature/manager/entities/FeatureSet.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.spring.cloud.feature.manager.entities; - -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.azure.spring.cloud.feature.manager.FeatureManager; - -@JsonIgnoreProperties(ignoreUnknown = true) -@ConfigurationProperties(prefix = "feature-management") -public class FeatureSet extends HashMap { - - private static final long serialVersionUID = -4565743301696064767L; - - private static Logger logger = LoggerFactory.getLogger(FeatureManager.class); - - public HashMap featureManagement; - - public HashMap onOff; - - private ObjectMapper mapper = new ObjectMapper(); - - public FeatureSet() { - featureManagement = new HashMap(); - onOff = new HashMap(); - } - - /** - * @return the featureManagement - */ - public HashMap getFeatureManagement() { - return featureManagement; - } - - public void setFeatureManagement(HashMap featureManagement) { - this.featureManagement = featureManagement; - } - - public void addFeature(Feature feature) { - HashMap features = new HashMap(); - features.put(feature.getKey(), feature); - addToFeatures(features, feature.getKey(), ""); - } - - @SuppressWarnings("unchecked") - private void addToFeatures(Map features, String key, String combined) { - Object featureKey = features.get(key); - if (!combined.isEmpty() && !combined.endsWith(".")) { - combined += "."; - } - if (featureKey instanceof Boolean) { - onOff.put(combined + key, (Boolean) featureKey); - } else { - Feature feature = null; - try { - feature = mapper.convertValue(featureKey, Feature.class); - } catch (IllegalArgumentException e) { - logger.error("Found invalid feature {} with value {}.", combined + key, featureKey.toString()); - } - - // When coming from a file "feature.flag" is not a possible flag name - if (feature != null && feature.getEnabledFor() == null && feature.getKey() == null) { - if (LinkedHashMap.class.isAssignableFrom(featureKey.getClass())) { - features = (LinkedHashMap) featureKey; - for (String fKey : features.keySet()) { - addToFeatures(features, fKey, combined + key); - } - } - } else { - if (feature != null) { - feature.setKey(key); - featureManagement.put(key, feature); - } - } - } - } - - /** - * @return the onOff - */ - public HashMap getOnOff() { - return onOff; - } - - @Override - public void putAll(Map m) { - if (m == null) { - return; - } - for (String key : m.keySet()) { - addToFeatures(m, key, ""); - } - } -} diff --git a/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagerTest.java b/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagerTest.java index 3fd83ee46..7b2db7959 100644 --- a/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagerTest.java +++ b/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureManagerTest.java @@ -29,7 +29,6 @@ import com.microsoft.azure.spring.cloud.feature.manager.entities.Feature; import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureFilterEvaluationContext; -import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureSet; /** * Unit tests for FeatureManager. @@ -50,9 +49,6 @@ public class FeatureManagerTest { @Mock private ApplicationContext context; - - @Mock - private FeatureSet featureSet; @Before public void setup() { @@ -65,11 +61,8 @@ public void setup() { */ @Test public void loadFeatureManagerWithLinkedHashSet() { - FeatureSet set = new FeatureSet(); Feature f = new Feature(); f.setKey(FEATURE_KEY); - - set.addFeature(f); LinkedHashMap testMap = new LinkedHashMap(); LinkedHashMap testFeature = new LinkedHashMap(); @@ -83,13 +76,12 @@ public void loadFeatureManagerWithLinkedHashSet() { testFeature.put("EnabledFor", enabledFor); testMap.put(f.getKey(), testFeature); - FeatureSet featureManagement = new FeatureSet(); - featureManagement.putAll(testMap); - assertNotNull(featureManagement); - assertNotNull(featureManagement.getFeatureManagement()); - assertEquals(1, featureManagement.getFeatureManagement().size()); - assertNotNull(featureManagement.getFeatureManagement().get(FEATURE_KEY)); - Feature feature = featureManagement.getFeatureManagement().get(FEATURE_KEY); + featureManager.putAll(testMap); + assertNotNull(featureManager); + assertNotNull(featureManager.getFeatureManagement()); + assertEquals(1, featureManager.getFeatureManagement().size()); + assertNotNull(featureManager.getFeatureManagement().get(FEATURE_KEY)); + Feature feature = featureManager.getFeatureManagement().get(FEATURE_KEY); assertEquals(FEATURE_KEY, feature.getKey()); assertEquals(1, feature.getEnabledFor().size()); FeatureFilterEvaluationContext zeroth = feature.getEnabledFor().get(0); @@ -100,42 +92,32 @@ public void loadFeatureManagerWithLinkedHashSet() { @Test public void isEnabledFeatureNotFound() throws InterruptedException, ExecutionException { - FeatureSet featureSet = new FeatureSet(); - HashMap features = new HashMap(); - featureSet.putAll(features); - FeatureManager featureManager = new FeatureManager(null, featureSet); - assertFalse(featureManager.isEnabledAsync("Non Existed Feature").block()); } @Test public void isEnabledFeatureOff() throws InterruptedException, ExecutionException { - FeatureSet featureSet = new FeatureSet(); HashMap features = new HashMap(); features.put("Off", false); - featureSet.putAll(features); - FeatureManager featureManager = new FeatureManager(null, featureSet); + featureManager.putAll(features); assertFalse(featureManager.isEnabledAsync("Off").block()); } @Test public void isEnabledFeatureHasNoFilters() throws InterruptedException, ExecutionException { - FeatureSet featureSet = new FeatureSet(); HashMap features = new HashMap(); Feature noFilters = new Feature(); noFilters.setKey("NoFilters"); noFilters.setEnabledFor(new ArrayList()); features.put("NoFilters", noFilters); - featureSet.putAll(features); - FeatureManager featureManager = new FeatureManager(null, featureSet); + featureManager.putAll(features); assertFalse(featureManager.isEnabledAsync("NoFilters").block()); } @Test public void isEnabledON() throws InterruptedException, ExecutionException { - FeatureSet featureSet = new FeatureSet(); HashMap features = new HashMap(); Feature onFeature = new Feature(); onFeature.setKey("On"); @@ -145,9 +127,7 @@ public void isEnabledON() throws InterruptedException, ExecutionException { filters.add(alwaysOn); onFeature.setEnabledFor(filters); features.put("On", onFeature); - featureSet.putAll(features); - when(this.featureSet.getFeatureManagement()).thenReturn(featureSet.getFeatureManagement()); - when(this.featureSet.getOnOff()).thenReturn(featureSet.getOnOff()); + featureManager.putAll(features); when(context.getBean(Mockito.matches("AlwaysOn"))).thenReturn(new AlwaysOn()); @@ -156,24 +136,21 @@ public void isEnabledON() throws InterruptedException, ExecutionException { @Test public void isEnabledOnBoolean() throws InterruptedException, ExecutionException { - FeatureSet featureSet = new FeatureSet(); HashMap features = new HashMap(); features.put("On", true); - featureSet.putAll(features); - FeatureManager featureManager = new FeatureManager(null, featureSet); + featureManager.putAll(features); assertTrue(featureManager.isEnabledAsync("On").block()); } @Test public void featureManagerNotEnabledCorrectly() throws InterruptedException, ExecutionException { - FeatureManager featureManager = new FeatureManager(null, null); + FeatureManager featureManager = new FeatureManager(null); assertFalse(featureManager.isEnabledAsync("").block()); } @Test public void bootstrapConfiguration() { - FeatureSet featureSet = new FeatureSet(); HashMap features = new HashMap(); features.put("FeatureU", false); Feature featureV = new Feature(); @@ -190,13 +167,13 @@ public void bootstrapConfiguration() { filterMapper.put(0, enabledFor); featureV.setFilterMapper(filterMapper); features.put("FeatureV", featureV); - featureSet.putAll(features); + featureManager.putAll(features); - assertNotNull(featureSet.getOnOff()); - assertNotNull(featureSet.getFeatureManagement()); + assertNotNull(featureManager.getOnOff()); + assertNotNull(featureManager.getFeatureManagement()); - assertEquals(featureSet.getOnOff().get("FeatureU"), false); - Feature feature = featureSet.getFeatureManagement().get("FeatureV"); + assertEquals(featureManager.getOnOff().get("FeatureU"), false); + Feature feature = featureManager.getFeatureManagement().get("FeatureV"); assertEquals(feature.getEnabledFor().size(), 1); FeatureFilterEvaluationContext ffec = feature.getEnabledFor().get(0); assertEquals(ffec.getName(), "Random"); diff --git a/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureSetTest.java b/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureSetTest.java deleted file mode 100644 index 96a1b0397..000000000 --- a/spring-cloud-azure-feature-management/src/test/java/com/microsoft/azure/spring/cloud/feature/manager/FeatureSetTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for - * license information. - */ -package com.microsoft.azure.spring.cloud.feature.manager; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.HashMap; -import java.util.LinkedHashMap; - -import org.junit.Test; - -import com.microsoft.azure.spring.cloud.feature.manager.entities.FeatureSet; - -public class FeatureSetTest { - - @Test - public void multipartFeatureFlagNameTest() { - HashMap features = new HashMap(); - LinkedHashMap featurePart = new LinkedHashMap(); - featurePart.put("A", true); - features.put("Beta", featurePart); - - FeatureSet featureSet = new FeatureSet(); - featureSet.putAll(features); - - assertEquals(1, featureSet.getOnOff().size()); - assertTrue(featureSet.getOnOff().get("Beta.A")); - } - - @Test - public void invalidFeatureFlagTest() { - HashMap features = new HashMap(); - LinkedHashMap featurePart = new LinkedHashMap(); - featurePart.put("A", 1); - features.put("Beta", featurePart); - - FeatureSet featureSet = new FeatureSet(); - featureSet.putAll(features); - - assertEquals(0, featureSet.getOnOff().size()); - - } - -} diff --git a/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md b/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md index 70b8741e7..086934b65 100644 --- a/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md +++ b/spring-cloud-azure-starters/spring-cloud-starter-azure-appconfiguration-config/README.md @@ -108,33 +108,32 @@ spring.cloud.azure.appconfiguration.fail-fast=false [Managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) allows application to access [Azure Active Directory][azure_active_directory] protected resource on [Azure][azure]. -In this library, [Azure Identity SDK][azure_identity_sdk] is used to access Azure App Configuration and optionally Azure Key Vault, for secrets. Only one method of authentication can be set at one time. +In this library, [Azure Identity SDK][azure_identity_sdk] is used to access Azure App Configuration and optionally Azure Key Vault, for secrets. Only one method of authentication can be set at one time. When not using the AppConfigCredentialProvider and/or KeyVaultCredentialProvider the same authentication method is used for both App Configuration and Key Vault. Follow the below steps to enable accessing App Configuration with managed identity: 1. [Enable managed identities](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview#how-can-i-use-managed-identities-for-azure-resources) for the [supported Azure services](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities), for example, virtual machine or App Service, on which the application will be deployed. -1. Configure the [Azure RBAC][azure_rbac] of your Application store to grant access to the Azure service where your application is running. Select the App Configuration Data Reader. The App Configuration Data Owner role is not required but can be used if needed. +1. Configure the [Azure RBAC][azure_rbac] of your App Configuration store to grant access to the Azure service where your application is running. Select the App Configuration Data Reader. The App Configuration Data Owner role is not required but can be used if needed. -1. Choose a configuration option: - 1. Set the Environment variable; AZURE_CLIENT_ID. - 1. Create a TokenCredentialProvider and supply any valid TokenCredential and supply it via a Bean. - 1. Configure bootstrap.properties(or .yaml) in the Spring Boot application. +1. Configure bootstrap.properties(or .yaml) in the Spring Boot application. -The configuration store name must be configured when `connection-string` is empty. +The configuration store endpoint must be configured when `connection-string` is empty. When using a User Assigned Id the value `spring.cloud.azure.appconfiguration.managed-identity.client-id=[client-id]` must be set. ### Token Credential Provider +Another method of authentication is using AppConfigCredentialProvider and/or KeyVaultCredentialProvider. By implementing either of these classes and providing and generating a @Bean of them will enable authentication through any method defined by the [Java Azure SDK][azure_identity_sdk]. + ```java public class MyCredentials implements AppConfigCredentialProvider, KeyVaultCredentialProvider { @Override - public TokenCredential credentialForAppConfig() { + public TokenCredential credentialForAppConfig(String uri) { return buildCredential(); } @Override - public TokenCredential credentialForKeyVault() { + public TokenCredential credentialForKeyVault(String uri) { return buildCredential(); } @@ -150,9 +149,6 @@ public class MyCredentials implements AppConfigCredentialProvider, KeyVaultCrede ```application spring.cloud.azure.appconfiguration.stores[0].endpoint=[config-store-endpoint] -#If Using option 3 -spring.cloud.azure.appconfiguration.managed-identity.client-id=[client-id] - #If Using option 3 spring.cloud.azure.appconfiguration.managed-identity.client-id=[client-id] ```