From 82f73569507c90d3a041dbc1dc69ef1d764fc7a0 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Mon, 9 Sep 2024 22:09:04 +0800 Subject: [PATCH 01/25] feat: configMap init --- apollo-client/pom.xml | 5 + .../apollo/Kubernetes/KubernetesManager.java | 79 ++++++++ .../apollo/enums/ConfigSourceType.java | 5 +- .../K8sConfigMapConfigRepository.java | 183 ++++++++++++++++++ .../internals/LocalFileConfigRepository.java | 7 + .../apollo/spi/DefaultConfigFactory.java | 33 ++-- .../framework/apollo/util/ConfigUtil.java | 51 +++++ ...itional-spring-configuration-metadata.json | 14 ++ .../K8sConfigMapConfigRepositoryTest.java | 141 ++++++++++++++ .../apollo/core/ApolloClientSystemConsts.java | 20 ++ .../framework/apollo/core/enums/Env.java | 2 +- 11 files changed, 526 insertions(+), 14 deletions(-) create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml index 7a8e436a..ce9d7d3b 100644 --- a/apollo-client/pom.xml +++ b/apollo-client/pom.xml @@ -98,5 +98,10 @@ test + + io.kubernetes + client-java + 18.0.0 + diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java new file mode 100644 index 00000000..7c6592ec --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -0,0 +1,79 @@ +package com.ctrip.framework.apollo.Kubernetes; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.util.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.Map; + + +@Service +public class KubernetesManager { + + private ApiClient client; + private CoreV1Api coreV1Api; + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + + @PostConstruct + public void initClient() { + try { + client = Config.defaultClient(); + Configuration.setDefaultApiClient(client); + coreV1Api = new CoreV1Api(client); + + } catch (Exception e) { + throw new RuntimeException("k8s client init failed"); + } + } + + public String createConfigMap(String configMapNamespace, String name, Map data) { + V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); + try { + coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null,null); + return name; + } catch (Exception e) { + log.error("create config map failed", e); + return null; + } + } + + public Map getFromConfigMap(String configMapNamespace, String name) { + try { + V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); + return configMap.getData(); + } catch (Exception e) { + log.error("get config map failed", e); + return null; + } + } + + public Map loadFromConfigMap(String configMapNamespace, String name, String key) { + try { + V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); + String jsonStr = configMap.getData().get(key); + + } catch (Exception e) { + log.error("get config map failed", e); + return null; + } + } + + public String updateConfigMap(String configMapNamespace, String name, Map data) { + try { + V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); + coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, null); + return name; + } catch (Exception e) { + log.error("update config map failed", e); + return null; + } + } + +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/enums/ConfigSourceType.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/enums/ConfigSourceType.java index b8249472..d3e3507e 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/enums/ConfigSourceType.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/enums/ConfigSourceType.java @@ -22,7 +22,10 @@ * @since 1.1.0 */ public enum ConfigSourceType { - REMOTE("Loaded from remote config service"), LOCAL("Loaded from local cache"), NONE("Load failed"); + REMOTE("Loaded from remote config service"), + LOCAL("Loaded from local cache"), + CONFIGMAP("Loaded from k8s config map"), + NONE("Load failed"); private final String description; diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java new file mode 100644 index 00000000..9660eb3d --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -0,0 +1,183 @@ +package com.ctrip.framework.apollo.internals; + +import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory; +import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.exceptions.ApolloConfigException; +import com.ctrip.framework.apollo.tracer.Tracer; +import com.ctrip.framework.apollo.tracer.spi.Transaction; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.ExceptionUtil; +import com.google.common.base.Preconditions; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * @author dyx1234 + */ +public class K8sConfigMapConfigRepository extends AbstractConfigRepository implements RepositoryChangeListener { + + private static final Logger logger = DeferredLoggerFactory.getLogger(K8sConfigMapConfigRepository.class); + private final String nameSpace; + private String configMapName; + private String configMapNamespace; + private final ConfigUtil configUtil; + private final KubernetesManager kubernetesManager; + private volatile Properties fileProperties; + private volatile ConfigRepository upstream; + private volatile ConfigSourceType sourceType = ConfigSourceType.CONFIGMAP; + + + /** + * Constructor + * + * @param namespace the namespace + */ + public K8sConfigMapConfigRepository(String namespace) { + this(namespace, null); + } + + public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) { + nameSpace = namespace; + configUtil = ApolloInjector.getInstance(ConfigUtil.class); + kubernetesManager = ApolloInjector.getInstance(KubernetesManager.class); + configMapNamespace = configUtil.getConfigMapNamespace(); + + this.setConfigMapName(configUtil.getAppId(), false); + this.setUpstreamRepository(upstream); + } + + void setConfigMapName(String appId, boolean syncImmediately){ + this.configMapName = appId; + if (syncImmediately) { + this.sync(); + } + } + + @Override + public Properties getConfig() { + if (fileProperties == null) { + sync(); + } + Properties result = propertiesFactory.getPropertiesInstance(); + result.putAll(fileProperties); + return result; + } + + /** + * Update the memory when the configuration center changes + * @param upstreamConfigRepository the upstream repo + */ + @Override + public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) { + if (upstreamConfigRepository == null) { + return; + } + //clear previous listener + if (upstream != null) { + upstream.removeChangeListener(this); + } + upstream = upstreamConfigRepository; + upstreamConfigRepository.addChangeListener(this); + } + + @Override + public ConfigSourceType getSourceType() { + return sourceType; + } + + /** + * Sync the configmap + */ + @Override + protected void sync() { + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncK8sConfigMap"); + try { + fileProperties = loadFromK8sConfigMap(); + sourceType = ConfigSourceType.CONFIGMAP; + transaction.setStatus(Transaction.SUCCESS); + } catch (Throwable ex) { + Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); + transaction.setStatus(ex); + sourceType = ConfigSourceType.NONE; + throw new ApolloConfigException("Load config from Kubernetes ConfigMap failed!", ex); + } finally { + transaction.complete(); + } + } + + // 职责明确: manager层进行序列化和解析,把key传进去 + // repo这里只负责更新内存, Properties和appConfig格式的兼容 + public Properties loadFromK8sConfigMap() throws IOException { + Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); + Properties properties = null; + try { + Map data = kubernetesManager.getFromConfigMap(configMapNamespace, configUtil.getAppId()); + properties = propertiesFactory.getPropertiesInstance(); + for (Map.Entry entry : data.entrySet()) { + String value = entry.getValue(); + if (value != null) { + value = new String(Base64.getDecoder().decode(value)); + } + properties.setProperty(entry.getKey(), value); + } + return properties; + } catch (Exception ex) { + logger.error("Failed to load config from Kubernetes ConfigMap: {}", configMapName, ex); + throw new IOException("Failed to load config from Kubernetes ConfigMap", ex); + } + } + + + /** + * Update the memory + * @param namespace the namespace of this repository change + * @param newProperties the properties after change + */ + @Override + public void onRepositoryChange(String namespace, Properties newProperties) { + if (newProperties.equals(fileProperties)) { + return; + } + Properties newFileProperties = propertiesFactory.getPropertiesInstance(); + newFileProperties.putAll(newProperties); + updateUpstreamProperties(newFileProperties, upstream.getSourceType()); + this.fireRepositoryChange(namespace, newProperties); + } + + private synchronized void updateUpstreamProperties(Properties newProperties, ConfigSourceType sourceType) { + this.sourceType = sourceType; + if (newProperties.equals(fileProperties)) { + return; + } + this.fileProperties = newProperties; + persistLocalCacheFile(fileProperties); + } + + public void persistLocalCacheFile(Properties properties) { + // 将Properties中的值持久化到configmap中,并使用事务管理 + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistLocalCacheFile"); + try { + Map data = new HashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + data.put(entry.getKey().toString(), Base64.getEncoder().encodeToString(entry.getValue().toString().getBytes())); + kubernetesManager.updateConfigMap(configUtil.getConfigMapNamespace(), configUtil.getAppId(), data); + transaction.addData("configMapName", configMapName); + transaction.addData("configMapNamespace", configMapNamespace); + transaction.setStatus(Transaction.SUCCESS); + } + } catch (Throwable ex) { + Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); + transaction.setStatus(ex); + throw new ApolloConfigException("Persist local cache file failed!", ex); + } + transaction.complete(); + } + +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java index 1820fd1a..f08579f1 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java @@ -57,6 +57,13 @@ public class LocalFileConfigRepository extends AbstractConfigRepository private volatile ConfigSourceType m_sourceType = ConfigSourceType.LOCAL; + /** + * configmapnamespace 用户配的,不配用默认default + * configmapname appid + * configmap-key cluster+namespace + * configmap-value 配置文件信息的json串 + */ + /** * Constructor. * diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java index 36c300f4..2aed0d2b 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java @@ -22,17 +22,7 @@ import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile; import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; -import com.ctrip.framework.apollo.internals.ConfigRepository; -import com.ctrip.framework.apollo.internals.DefaultConfig; -import com.ctrip.framework.apollo.internals.JsonConfigFile; -import com.ctrip.framework.apollo.internals.LocalFileConfigRepository; -import com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository; -import com.ctrip.framework.apollo.internals.PropertiesConfigFile; -import com.ctrip.framework.apollo.internals.RemoteConfigRepository; -import com.ctrip.framework.apollo.internals.TxtConfigFile; -import com.ctrip.framework.apollo.internals.XmlConfigFile; -import com.ctrip.framework.apollo.internals.YamlConfigFile; -import com.ctrip.framework.apollo.internals.YmlConfigFile; +import com.ctrip.framework.apollo.internals.*; import com.ctrip.framework.apollo.util.ConfigUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +67,7 @@ public Config create(String namespace) { configRepository = createPropertiesCompatibleFileConfigRepository(namespace, format); } else { configRepository = createConfigRepository(namespace); + //ServiceBootstrap.loadPrimary(ConfigRepository.class); } logger.debug("Created a configuration repository of type [{}] for namespace [{}]", @@ -111,7 +102,10 @@ public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFile } ConfigRepository createConfigRepository(String namespace) { - if (m_configUtil.isPropertyFileCacheEnabled()) { + // TODO 本地和configMap允许同时开启 + if (m_configUtil.isPropertyKubernetesCacheEnabled()) { + return createConfigMapConfigRepository(namespace); + }else if (m_configUtil.isPropertyFileCacheEnabled()) { return createLocalConfigRepository(namespace); } return createRemoteConfigRepository(namespace); @@ -133,6 +127,21 @@ LocalFileConfigRepository createLocalConfigRepository(String namespace) { return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace)); } + /** + * Creates a Kubernetes config map repository for a given namespace + * @param namespace the namespace of the repository + * @return the newly created repository for the given namespace + */ + private ConfigRepository createConfigMapConfigRepository(String namespace) { + if (m_configUtil.isInKubernetesMode()) { + logger.warn( + "==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====", + namespace); + return new K8sConfigMapConfigRepository(namespace); + } + return new K8sConfigMapConfigRepository(namespace, createLocalConfigRepository(namespace)); + } + RemoteConfigRepository createRemoteConfigRepository(String namespace) { return new RemoteConfigRepository(namespace); } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index 9b99a4bf..e5af968a 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -65,12 +65,15 @@ public class ConfigUtil { private long configCacheExpireTime = 1;//1 minute private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute private long longPollingInitialDelayInMills = 2000;//2 seconds + private String kubernetesCacheConfigMapNamespace; + private final String DEFAULT_KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE = "default"; private boolean autoUpdateInjectedSpringProperties = true; private final RateLimiter warnLogRateLimiter; private boolean propertiesOrdered = false; private boolean propertyNamesCacheEnabled = false; private boolean propertyFileCacheEnabled = true; private boolean overrideSystemProperties = true; + private boolean PropertyKubernetesCacheEnabled = false; public ConfigUtil() { warnLogRateLimiter = RateLimiter.create(0.017); // 1 warning log output per minute @@ -86,6 +89,7 @@ public ConfigUtil() { initPropertyNamesCacheEnabled(); initPropertyFileCacheEnabled(); initOverrideSystemProperties(); + initPropertyKubernetesCacheEnabled(); } /** @@ -366,6 +370,34 @@ private String getDeprecatedCustomizedCacheRoot() { return cacheRoot; } + public String getConfigMapNamespace() { + String configMapNamespace = getCustomizedConfigMapNamespace(); + + if (!Strings.isNullOrEmpty(configMapNamespace)) { + return configMapNamespace; + } + + return DEFAULT_KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE; + } + + private String getCustomizedConfigMapNamespace() { + // 1. Get from System Property + String configMapNamespace = System.getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE); + if (!Strings.isNullOrEmpty(configMapNamespace)) { + // 2. Get from OS environment variable + configMapNamespace = System.getenv(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES); + } + if (Strings.isNullOrEmpty(configMapNamespace)) { + // 3. Get from server.properties + configMapNamespace = Foundation.server().getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, null); + } + if (Strings.isNullOrEmpty(configMapNamespace)) { + // 4. Get from app.properties + configMapNamespace = Foundation.app().getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, null); + } + return configMapNamespace; + } + public boolean isInLocalMode() { try { return Env.LOCAL == getApolloEnv(); @@ -375,6 +407,15 @@ public boolean isInLocalMode() { return false; } + public boolean isInKubernetesMode() { + try { + return Env.KUBERNETES == getApolloEnv(); + } catch (Throwable ex) { + //ignore + } + return false; + } + public boolean isOSWindows() { String osName = System.getProperty("os.name"); if (Strings.isNullOrEmpty(osName)) { @@ -469,6 +510,10 @@ public boolean isPropertyFileCacheEnabled() { return propertyFileCacheEnabled; } + public boolean isPropertyKubernetesCacheEnabled() { + return PropertyKubernetesCacheEnabled; + } + public boolean isOverrideSystemProperties() { return overrideSystemProperties; } @@ -491,6 +536,12 @@ private void initOverrideSystemProperties() { overrideSystemProperties); } + private void initPropertyKubernetesCacheEnabled() { + PropertyKubernetesCacheEnabled = getPropertyBoolean(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE, + ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE_ENVIRONMENT_VARIABLES, + PropertyKubernetesCacheEnabled); + } + private boolean getPropertyBoolean(String propertyName, String envName, boolean defaultVal) { String enablePropertyNamesCache = System.getProperty(propertyName); if (Strings.isNullOrEmpty(enablePropertyNamesCache)) { diff --git a/apollo-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/apollo-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json index a14b1d03..d49b372a 100644 --- a/apollo-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/apollo-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -76,6 +76,20 @@ "description": "enable property names cache.", "defaultValue": false }, + { + "name": "apollo.cache.kubernetes.enable", + "type": "java.lang.Boolean", + "sourceType": "com.ctrip.framework.apollo.util.ConfigUtil", + "description": "enable kubernetes configmap cache.", + "defaultValue": false + }, + { + "name": "apollo.cache.kubernetes.namespace", + "type": "java.lang.String", + "sourceType": "com.ctrip.framework.apollo.util.ConfigUtil", + "description": "kubernetes configmap namespace.", + "defaultValue": "default" + }, { "name": "apollo.property.order.enable", "type": "java.lang.Boolean", diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java new file mode 100644 index 00000000..e7feb0de --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -0,0 +1,141 @@ +package com.ctrip.framework.apollo.internals; + +import com.ctrip.framework.apollo.util.ConfigUtil; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.exceptions.ApolloConfigException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + + +/** + * author: dyx1234 + */ + +@RunWith(MockitoJUnitRunner.class) +public class K8sConfigMapConfigRepositoryTest { + + @Mock + private KubernetesManager kubernetesManager; + + @Mock + private ConfigUtil configUtil; + + @InjectMocks + private K8sConfigMapConfigRepository repository; + + @Before + public void setUp() { + Mockito.when(configUtil.getAppId()).thenReturn("TestApp"); + Mockito.when(configUtil.getConfigMapNamespace()).thenReturn("TestNamespace"); + } + + @Test + public void testLoadFromK8sConfigMapSuccess() throws IOException { + // 安排 + Map expectedData = new HashMap<>(); + expectedData.put("key1", "c2FtcGxlIHN0cmluZw=="); // base64 编码后的 "sample string" + Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) + .thenReturn(expectedData); + + // 执行 + Properties properties = repository.loadFromK8sConfigMap(); + + // 断言 + assertEquals(1, properties.size()); + assertEquals("sample string", properties.getProperty("key1")); + } + + @Test(expected = ApolloConfigException.class) + public void testLoadFromK8sConfigMapFailure() throws IOException { + // 安排 + Mockito.doThrow(new RuntimeException("Kubernetes Manager Exception")) + .when(kubernetesManager).getFromConfigMap("TestNamespace", "TestApp"); + + // 执行 & 断言 - 会抛出异常 + repository.loadFromK8sConfigMap(); + } + + @Test + public void testOnRepositoryChange() throws IOException { + // 安排 + Properties newProperties = new Properties(); + newProperties.setProperty("key1", "value1"); + + // 执行 + repository.onRepositoryChange("TestNamespace", newProperties); + + // 断言 + assertEquals(1, repository.getConfig().size()); + assertEquals("value1", repository.getConfig().getProperty("key1")); + } + + @Test + public void testSyncSuccess() { + Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) + .thenReturn(java.util.Collections.singletonMap("key", "value")); + + Properties properties = repository.getConfig(); + + assertEquals(1, properties.size()); + assertEquals("value", properties.getProperty("key")); + } + + @Test(expected = ApolloConfigException.class) + public void testSyncFailure() { + Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) + .thenThrow(new RuntimeException("Kubernetes Manager Exception")); + + repository.getConfig(); + } + + @Test + public void testUpstreamRepositoryChange() { + Properties upstreamProperties = new Properties(); + upstreamProperties.setProperty("upstreamKey", "upstreamValue"); + + ConfigRepository mockUpstream = mock(ConfigRepository.class); + when(mockUpstream.getConfig()).thenReturn(upstreamProperties); + + repository.setUpstreamRepository(mockUpstream); + + // Assuming onRepositoryChange is called upon change + repository.onRepositoryChange("upstream", upstreamProperties); + + Properties result = repository.getConfig(); + + assertEquals("upstreamValue", result.getProperty("upstreamKey")); + } + + @Test + public void testPersistLocalCacheFileSuccess() { + Properties properties = new Properties(); + properties.setProperty("persistKey", "persistValue"); + + repository.persistLocalCacheFile(properties); + + verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); + } + + @Test(expected = ApolloConfigException.class) + public void testPersistLocalCacheFileFailure() { + Properties properties = new Properties(); + properties.setProperty("persistKey", "persistValue"); + + doThrow(new RuntimeException("Kubernetes Manager Exception")).when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); + + repository.persistLocalCacheFile(properties); + } +} diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java index 0fa0550a..0e47a664 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java @@ -73,6 +73,16 @@ public class ApolloClientSystemConsts { @Deprecated public static final String DEPRECATED_APOLLO_CACHE_DIR_ENVIRONMENT_VARIABLES = "APOLLO_CACHEDIR"; + /** + * kubernetes configmap cache namespace + */ + public static final String APOLLO_CONFIGMAP_NAMESPACE = "apollo.configmap-namespace"; + + /** + * kubernetes configmap cache namespace environment variables + */ + public static final String APOLLO_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES = "APOLLO_CONFIGMAP_NAMESPACE"; + /** * apollo client access key */ @@ -157,6 +167,16 @@ public class ApolloClientSystemConsts { */ public static final String APOLLO_CACHE_FILE_ENABLE_ENVIRONMENT_VARIABLES = "APOLLO_CACHE_FILE_ENABLE"; + /** + * enable property names cache + */ + public static final String APOLLO_KUBERNETES_CACHE_ENABLE = "apollo.cache.kubernetes.enable"; + + /** + * enable property names cache environment variables + */ + public static final String APOLLO_KUBERNETES_CACHE_ENABLE_ENVIRONMENT_VARIABLES = "APOLLO_KUBERNETES_CACHE_ENABLE"; + /** * enable apollo overrideSystemProperties */ diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java index 71a682e5..083d4d2d 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java @@ -35,7 +35,7 @@ * @author Jason Song(song_s@ctrip.com) */ public enum Env{ - LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN; + LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN, KUBERNETES; public static Env fromString(String env) { Env environment = EnvUtils.transformEnv(env); From e14fd006cf16df5d76b46dbfbf46d8c7a827d474 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 11 Sep 2024 23:21:26 +0800 Subject: [PATCH 02/25] =?UTF-8?q?feat=EF=BC=9Aunit=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apollo/Kubernetes/KubernetesManager.java | 62 ++++- .../K8sConfigMapConfigRepository.java | 187 ++++++++++--- .../internals/LocalFileConfigRepository.java | 7 - .../framework/apollo/util/ConfigUtil.java | 6 +- .../Kubernetes/KubernetesManagerTest.java | 261 ++++++++++++++++++ .../K8sConfigMapConfigRepositoryTest.java | 141 ---------- .../framework/apollo/util/ConfigUtilTest.java | 18 ++ .../framework/apollo/core/ConfigConsts.java | 1 + 8 files changed, 485 insertions(+), 198 deletions(-) create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index 7c6592ec..0c18f179 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -12,14 +12,12 @@ import javax.annotation.PostConstruct; import java.util.Map; - @Service public class KubernetesManager { - private ApiClient client; private CoreV1Api coreV1Api; - private final Logger log = LoggerFactory.getLogger(this.getClass()); + private final Logger log = LoggerFactory.getLogger(this.getClass()); @PostConstruct public void initClient() { @@ -34,6 +32,10 @@ public void initClient() { } public String createConfigMap(String configMapNamespace, String name, Map data) { + if (configMapNamespace == null || configMapNamespace == "" || name == null || name == "") { + log.error("create config map failed due to null parameter"); + return null; + } V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); try { coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null,null); @@ -44,21 +46,46 @@ public String createConfigMap(String configMapNamespace, String name, Map getFromConfigMap(String configMapNamespace, String name) { + public String loadFromConfigMap(String configMapNamespace, String name) { + if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || name == null || name.isEmpty()) { + log.error("参数不能为空"); + return null; + } try { V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); - return configMap.getData(); + if (configMap == null) { + log.error("ConfigMap不存在"); + return null; + } + Map data = configMap.getData(); + if (data != null && data.containsKey(name)) { + return data.get(name); + } else { + log.error("在ConfigMap中未找到指定的键: " + name); + return null; + } } catch (Exception e) { log.error("get config map failed", e); return null; } } - public Map loadFromConfigMap(String configMapNamespace, String name, String key) { + public String getValueFromConfigMap(String configMapNamespace, String name, String key) { + if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || key == null || key.isEmpty()) { + log.error("参数不能为空"); + return null; + } try { V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); - String jsonStr = configMap.getData().get(key); - + if (configMap == null || configMap.getData() == null) { + log.error("ConfigMap不存在或没有数据"); + return null; + } + if (!configMap.getData().containsKey(key)) { + log.error("在ConfigMap中未找到指定的键: " + key); + return null; + } + return configMap.getData().get(key); } catch (Exception e) { log.error("get config map failed", e); return null; @@ -66,9 +93,13 @@ public Map loadFromConfigMap(String configMapNamespace, String n } public String updateConfigMap(String configMapNamespace, String name, Map data) { + if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || data == null || data.isEmpty()) { + log.error("参数不能为空"); + return null; + } try { V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); - coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, null); + coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue"); return name; } catch (Exception e) { log.error("update config map failed", e); @@ -76,4 +107,17 @@ public String updateConfigMap(String configMapNamespace, String name, Mapidc>default,所以不需要额外层级设置了 + if (StringUtils.isBlank(cluster)){ + configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); + return; + } + configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(cluster, namespace); + } + void setConfigMapName(String appId, boolean syncImmediately){ - this.configMapName = appId; + configMapName = appId; + this.checkConfigMapName(configMapName); if (syncImmediately) { this.sync(); } } + private void checkConfigMapName(String configMapName) { + if (StringUtils.isBlank(configMapName)) { + throw new ApolloConfigException("ConfigMap name cannot be null"); + } + // 判断configMap是否存在,若存在直接返回,若不存在尝试创建 + if(kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)){ + return; + } + // TODO 初步理解这里只生成就可以,后续update事件再写入新值 + + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createK8sConfigMap"); + transaction.addData("configMapName", configMapName); + try { + kubernetesManager.createConfigMap(configMapNamespace, configMapName, null); + transaction.setStatus(Transaction.SUCCESS); + } catch (Throwable ex) { + Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); + transaction.setStatus(ex); + throw new ApolloConfigException("Create configmap failed!", ex); + } finally { + transaction.complete(); + } + } + @Override public Properties getConfig() { - if (fileProperties == null) { + if (configMapProperties == null) { sync(); } Properties result = propertiesFactory.getPropertiesInstance(); - result.putAll(fileProperties); + result.putAll(configMapProperties); return result; } @@ -76,6 +132,7 @@ public Properties getConfig() { */ @Override public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) { + // 设置上游数据源 if (upstreamConfigRepository == null) { return; } @@ -97,43 +154,89 @@ public ConfigSourceType getSourceType() { */ @Override protected void sync() { + // 链式恢复,先从上游数据源读取 + boolean syncFromUpstreamResultSuccess = trySyncFromUpstream(); + + if (syncFromUpstreamResultSuccess) { + return; + } + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncK8sConfigMap"); + Throwable exception = null; try { - fileProperties = loadFromK8sConfigMap(); + configMapProperties = loadFromK8sConfigMap(); sourceType = ConfigSourceType.CONFIGMAP; transaction.setStatus(Transaction.SUCCESS); } catch (Throwable ex) { Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); transaction.setStatus(ex); - sourceType = ConfigSourceType.NONE; + exception = ex; throw new ApolloConfigException("Load config from Kubernetes ConfigMap failed!", ex); } finally { transaction.complete(); } + + if (configMapProperties == null) { + sourceType = ConfigSourceType.NONE; + throw new ApolloConfigException( + "Load config from Kubernetes ConfigMap failed!", exception); + } } // 职责明确: manager层进行序列化和解析,把key传进去 // repo这里只负责更新内存, Properties和appConfig格式的兼容 public Properties loadFromK8sConfigMap() throws IOException { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); + Properties properties = null; try { - Map data = kubernetesManager.getFromConfigMap(configMapNamespace, configUtil.getAppId()); + // 从ConfigMap获取整个配置信息的JSON字符串 + String jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configUtil.getAppId(), configMapKey); + + // 确保获取到的配置信息不为空 + if (jsonConfig != null) { + // 解码Base64编码的JSON字符串 + jsonConfig = new String(Base64.getDecoder().decode(jsonConfig)); + } + + // 创建Properties实例 properties = propertiesFactory.getPropertiesInstance(); - for (Map.Entry entry : data.entrySet()) { - String value = entry.getValue(); - if (value != null) { - value = new String(Base64.getDecoder().decode(value)); + + // 使用Gson将JSON字符串转换为Map对象 + if (jsonConfig != null && !jsonConfig.isEmpty()) { + Gson gson = new Gson(); + Type type = new TypeToken>() {}.getType(); + Map configMap = gson.fromJson(jsonConfig, type); + // 将Map中的键值对填充到Properties对象中 + for (Map.Entry entry : configMap.entrySet()) { + properties.setProperty(entry.getKey(), entry.getValue()); } - properties.setProperty(entry.getKey(), value); } return properties; } catch (Exception ex) { - logger.error("Failed to load config from Kubernetes ConfigMap: {}", configMapName, ex); - throw new IOException("Failed to load config from Kubernetes ConfigMap", ex); + Tracer.logError(ex); + throw new ApolloConfigException(String + .format("Load config from Kubernetes ConfigMap %s failed!", configMapName), ex); } } + private boolean trySyncFromUpstream() { + if (upstream == null) { + return false; + } + try { + // TODO 从上游数据恢复的逻辑 + // 拉新数据,并将新数据更新到configMap + updateConfigMapProperties(upstream.getConfig(), upstream.getSourceType()); + return true; + } catch (Throwable ex) { + Tracer.logError(ex); + logger + .warn("Sync config from upstream repository {} failed, reason: {}", upstream.getClass(), + ExceptionUtil.getDetailMessage(ex)); + } + return false; + } /** * Update the memory @@ -142,40 +245,50 @@ public Properties loadFromK8sConfigMap() throws IOException { */ @Override public void onRepositoryChange(String namespace, Properties newProperties) { - if (newProperties.equals(fileProperties)) { + if (newProperties.equals(configMapProperties)) { return; } Properties newFileProperties = propertiesFactory.getPropertiesInstance(); newFileProperties.putAll(newProperties); - updateUpstreamProperties(newFileProperties, upstream.getSourceType()); + updateConfigMapProperties(newFileProperties, upstream.getSourceType()); this.fireRepositoryChange(namespace, newProperties); } - private synchronized void updateUpstreamProperties(Properties newProperties, ConfigSourceType sourceType) { + private synchronized void updateConfigMapProperties(Properties newProperties, ConfigSourceType sourceType) { this.sourceType = sourceType; - if (newProperties.equals(fileProperties)) { + if (newProperties.equals(configMapProperties)) { return; } - this.fileProperties = newProperties; - persistLocalCacheFile(fileProperties); + this.configMapProperties = newProperties; + persistConfigMap(configMapProperties); } - public void persistLocalCacheFile(Properties properties) { + public void persistConfigMap(Properties properties) { // 将Properties中的值持久化到configmap中,并使用事务管理 - Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistLocalCacheFile"); + Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); + transaction.addData("configMapName", configUtil.getAppId()); + transaction.addData("configMapNamespace", configUtil.getConfigMapNamespace()); try { + // 使用Gson将properties转换为JSON字符串 + Gson gson = new Gson(); + String jsonConfig = gson.toJson(properties); + String encodedJsonConfig = Base64.getEncoder().encodeToString(jsonConfig.getBytes()); + // 创建一个新的HashMap, 将编码后的JSON字符串作为值,业务appId作为键,存入data中 Map data = new HashMap<>(); - for (Map.Entry entry : properties.entrySet()) { - data.put(entry.getKey().toString(), Base64.getEncoder().encodeToString(entry.getValue().toString().getBytes())); - kubernetesManager.updateConfigMap(configUtil.getConfigMapNamespace(), configUtil.getAppId(), data); - transaction.addData("configMapName", configMapName); - transaction.addData("configMapNamespace", configMapNamespace); - transaction.setStatus(Transaction.SUCCESS); - } - } catch (Throwable ex) { - Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); - transaction.setStatus(ex); - throw new ApolloConfigException("Persist local cache file failed!", ex); + data.put(configUtil.getAppId(), encodedJsonConfig); + + // 更新ConfigMap + kubernetesManager.updateConfigMap(configUtil.getConfigMapNamespace(), configUtil.getAppId(), data); + transaction.setStatus(Transaction.SUCCESS); + } catch (Exception ex) { + ApolloConfigException exception = + new ApolloConfigException( + String.format("Persist config to Kubernetes ConfigMap %s failed!", configMapName), ex); + Tracer.logError(exception); + transaction.setStatus(exception); + logger.error("Persist config to Kubernetes ConfigMap failed!", exception); + } finally { + transaction.complete(); } transaction.complete(); } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java index f08579f1..1820fd1a 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepository.java @@ -57,13 +57,6 @@ public class LocalFileConfigRepository extends AbstractConfigRepository private volatile ConfigSourceType m_sourceType = ConfigSourceType.LOCAL; - /** - * configmapnamespace 用户配的,不配用默认default - * configmapname appid - * configmap-key cluster+namespace - * configmap-value 配置文件信息的json串 - */ - /** * Constructor. * diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index e5af968a..d3c74c4d 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -65,8 +65,6 @@ public class ConfigUtil { private long configCacheExpireTime = 1;//1 minute private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute private long longPollingInitialDelayInMills = 2000;//2 seconds - private String kubernetesCacheConfigMapNamespace; - private final String DEFAULT_KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE = "default"; private boolean autoUpdateInjectedSpringProperties = true; private final RateLimiter warnLogRateLimiter; private boolean propertiesOrdered = false; @@ -377,13 +375,13 @@ public String getConfigMapNamespace() { return configMapNamespace; } - return DEFAULT_KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE; + return ConfigConsts.KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE_DEFAULT; } private String getCustomizedConfigMapNamespace() { // 1. Get from System Property String configMapNamespace = System.getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE); - if (!Strings.isNullOrEmpty(configMapNamespace)) { + if (Strings.isNullOrEmpty(configMapNamespace)) { // 2. Get from OS environment variable configMapNamespace = System.getenv(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES); } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java new file mode 100644 index 00000000..6e1bb9e5 --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java @@ -0,0 +1,261 @@ +package com.ctrip.framework.apollo.Kubernetes; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +@RunWith(MockitoJUnitRunner.class) +public class KubernetesManagerTest { + + @Mock + private CoreV1Api coreV1Api; + @Mock + private ApiClient client; + + @InjectMocks + private KubernetesManager kubernetesManager; + + /** + * 测试 createConfigMap 成功创建配置 + */ + @Test + public void testCreateConfigMapSuccess() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + Map data = new HashMap<>(); + data.put("key", "value"); + V1ConfigMap configMap = new V1ConfigMap() + .metadata(new V1ObjectMeta().name(name).namespace(namespace)) + .data(data); + + when(coreV1Api.createNamespacedConfigMap(eq(namespace), eq(configMap), isNull(), isNull(), isNull(),isNull())).thenReturn(configMap); + + // act + String result = kubernetesManager.createConfigMap(namespace, name, data); + + // assert + verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); + assert name.equals(result); + } + + /** + * 测试 createConfigMap 传入空的命名空间 + */ + @Test + public void testCreateConfigMapEmptyNamespace() throws Exception { + // arrange + String namespace = ""; + String name = "testConfigMap"; + Map data = new HashMap<>(); + data.put("key", "value"); + + // act + String result = kubernetesManager.createConfigMap(namespace, name, data); + + // assert + verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); + assert result == null; + } + + /** + * 测试 createConfigMap 传入空的配置名 + */ + @Test + public void testCreateConfigMapEmptyName() throws Exception { + // arrange + String namespace = "default"; + String name = ""; + Map data = new HashMap<>(); + data.put("key", "value"); + + // act + String result = kubernetesManager.createConfigMap(namespace, name, data); + + // assert + verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); + assert result == null; + } + + /** + * 测试 createConfigMap 传入 null 作为数据 + */ + @Test + public void testCreateConfigMapNullData() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + Map data = null; + + // act + String result = kubernetesManager.createConfigMap(namespace, name, data); + + // assert + verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); + assert name.equals(result); + } + + /** + * 测试loadFromConfigMap方法在正常情况下的行为 + */ + @Test + public void testLoadFromConfigMapSuccess() throws Exception { + // arrange + String namespace = "TestNamespace"; + String name = "TestName"; + V1ConfigMap configMap = new V1ConfigMap(); + configMap.putDataItem(name, "TestValue"); + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); + + // act + String result = kubernetesManager.loadFromConfigMap(namespace, name); + + // assert + assertEquals("TestValue", result); + } + + /** + * 测试loadFromConfigMap方法在抛出异常时的行为 + */ + @Test + public void testLoadFromConfigMapFailure() throws Exception { + // arrange + String namespace = "TestNamespace"; + String name = "TestName"; + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenThrow(new ApiException("Kubernetes Manager Exception")); + + // act + String result = kubernetesManager.loadFromConfigMap(namespace, name); + + // assert + assertNull(result); + } + + /** + * 测试loadFromConfigMap方法在ConfigMap不存在时的行为 + */ + @Test + public void testLoadFromConfigMapConfigMapNotFound() throws Exception { + // arrange + String namespace = "TestNamespace"; + String name = "TestName"; + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(null); + + // act + String result = kubernetesManager.loadFromConfigMap(namespace, name); + + // assert + assertNull(result); + } + + /** + * 测试getValueFromConfigMap方法,当ConfigMap存在且包含指定key时返回正确的value + */ + @Test + public void testGetValueFromConfigMapReturnsValue() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + String key = "testKey"; + String expectedValue = "testValue"; + V1ConfigMap configMap = new V1ConfigMap(); + configMap.putDataItem(key, expectedValue); + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); + + // act + String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); + + // assert + assertEquals(expectedValue, actualValue); + } + + /** + * 测试getValueFromConfigMap方法,当ConfigMap不存在指定key时返回null + */ + @Test + public void testGetValueFromConfigMapKeyNotFound() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + String key = "nonExistingKey"; + V1ConfigMap configMap = new V1ConfigMap(); + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); + + // act + String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); + + // assert + assertNull(actualValue); + } + + /** + * 测试updateConfigMap成功的情况 + */ + @Test + public void testUpdateConfigMapSuccess() throws Exception { + // arrange + String configMapNamespace = "default"; + String name = "testConfigMap"; + Map data = new HashMap<>(); + data.put("key", "value"); + V1ConfigMap configMap = new V1ConfigMap(); + configMap.metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)); + configMap.data(data); + + when(coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue")).thenReturn(configMap); + + // act + String result = kubernetesManager.updateConfigMap(configMapNamespace, name, data); + + // assert + assert result.equals(name); + } + + /** + * 测试ConfigMap存在的情况 + */ + @Test + public void testCheckConfigMapExistWhenConfigMapExists() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(new V1ConfigMap()); + + // act + boolean result = kubernetesManager.checkConfigMapExist(namespace, name); + + // assert + assertTrue(result); + } + + /** + * 测试ConfigMap不存在的情况 + */ + @Test + public void testCheckConfigMapExistWhenConfigMapDoesNotExist() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + doThrow(new RuntimeException("ConfigMap not found")).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); + + // act + boolean result = kubernetesManager.checkConfigMapExist(namespace, name); + + // assert + assertFalse(result); + } + +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index e7feb0de..e69de29b 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -1,141 +0,0 @@ -package com.ctrip.framework.apollo.internals; - -import com.ctrip.framework.apollo.util.ConfigUtil; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import static org.mockito.Mockito.*; -import static org.junit.Assert.*; - -import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; -import com.ctrip.framework.apollo.exceptions.ApolloConfigException; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - - -/** - * author: dyx1234 - */ - -@RunWith(MockitoJUnitRunner.class) -public class K8sConfigMapConfigRepositoryTest { - - @Mock - private KubernetesManager kubernetesManager; - - @Mock - private ConfigUtil configUtil; - - @InjectMocks - private K8sConfigMapConfigRepository repository; - - @Before - public void setUp() { - Mockito.when(configUtil.getAppId()).thenReturn("TestApp"); - Mockito.when(configUtil.getConfigMapNamespace()).thenReturn("TestNamespace"); - } - - @Test - public void testLoadFromK8sConfigMapSuccess() throws IOException { - // 安排 - Map expectedData = new HashMap<>(); - expectedData.put("key1", "c2FtcGxlIHN0cmluZw=="); // base64 编码后的 "sample string" - Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) - .thenReturn(expectedData); - - // 执行 - Properties properties = repository.loadFromK8sConfigMap(); - - // 断言 - assertEquals(1, properties.size()); - assertEquals("sample string", properties.getProperty("key1")); - } - - @Test(expected = ApolloConfigException.class) - public void testLoadFromK8sConfigMapFailure() throws IOException { - // 安排 - Mockito.doThrow(new RuntimeException("Kubernetes Manager Exception")) - .when(kubernetesManager).getFromConfigMap("TestNamespace", "TestApp"); - - // 执行 & 断言 - 会抛出异常 - repository.loadFromK8sConfigMap(); - } - - @Test - public void testOnRepositoryChange() throws IOException { - // 安排 - Properties newProperties = new Properties(); - newProperties.setProperty("key1", "value1"); - - // 执行 - repository.onRepositoryChange("TestNamespace", newProperties); - - // 断言 - assertEquals(1, repository.getConfig().size()); - assertEquals("value1", repository.getConfig().getProperty("key1")); - } - - @Test - public void testSyncSuccess() { - Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) - .thenReturn(java.util.Collections.singletonMap("key", "value")); - - Properties properties = repository.getConfig(); - - assertEquals(1, properties.size()); - assertEquals("value", properties.getProperty("key")); - } - - @Test(expected = ApolloConfigException.class) - public void testSyncFailure() { - Mockito.when(kubernetesManager.getFromConfigMap("TestNamespace", "TestApp")) - .thenThrow(new RuntimeException("Kubernetes Manager Exception")); - - repository.getConfig(); - } - - @Test - public void testUpstreamRepositoryChange() { - Properties upstreamProperties = new Properties(); - upstreamProperties.setProperty("upstreamKey", "upstreamValue"); - - ConfigRepository mockUpstream = mock(ConfigRepository.class); - when(mockUpstream.getConfig()).thenReturn(upstreamProperties); - - repository.setUpstreamRepository(mockUpstream); - - // Assuming onRepositoryChange is called upon change - repository.onRepositoryChange("upstream", upstreamProperties); - - Properties result = repository.getConfig(); - - assertEquals("upstreamValue", result.getProperty("upstreamKey")); - } - - @Test - public void testPersistLocalCacheFileSuccess() { - Properties properties = new Properties(); - properties.setProperty("persistKey", "persistValue"); - - repository.persistLocalCacheFile(properties); - - verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); - } - - @Test(expected = ApolloConfigException.class) - public void testPersistLocalCacheFileFailure() { - Properties properties = new Properties(); - properties.setProperty("persistKey", "persistValue"); - - doThrow(new RuntimeException("Kubernetes Manager Exception")).when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); - - repository.persistLocalCacheFile(properties); - } -} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index 23a37362..b9f88a53 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -243,6 +243,24 @@ public void testDefaultLocalCacheDir() throws Exception { assertEquals("/opt/data/" + someAppId, configUtil.getDefaultLocalCacheDir()); } + @Test + public void testConfigMapNamespaceWithSystemProperty() { + String someConfigMapNamespace = "someConfigMapNamespace"; + + System.setProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, someConfigMapNamespace); + + ConfigUtil configUtil = new ConfigUtil(); + + assertEquals(someConfigMapNamespace, configUtil.getConfigMapNamespace()); + } + + @Test + public void testConfigMapNamespaceWithDefault() { + ConfigUtil configUtil = new ConfigUtil(); + + assertEquals(ConfigConsts.KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE_DEFAULT, configUtil.getConfigMapNamespace()); + } + @Test public void testCustomizePropertiesOrdered() { boolean propertiesOrdered = true; diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java index a6108dfb..d6370ec0 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java @@ -24,5 +24,6 @@ public interface ConfigConsts { String APOLLO_META_KEY = "apollo.meta"; String CONFIG_FILE_CONTENT_KEY = "content"; String NO_APPID_PLACEHOLDER = "ApolloNoAppIdPlaceHolder"; + String KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE_DEFAULT = "default"; long NOTIFICATION_ID_PLACEHOLDER = -1; } From 562b325383fa9bf70bc01fb8827179a300a265f6 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Fri, 13 Sep 2024 00:53:26 +0800 Subject: [PATCH 03/25] =?UTF-8?q?feat=EF=BC=9ACopyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apollo/Kubernetes/KubernetesManager.java | 16 ++++++++++++++++ .../internals/K8sConfigMapConfigRepository.java | 16 ++++++++++++++++ .../apollo/Kubernetes/KubernetesManagerTest.java | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index 0c18f179..bb63a6f0 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.ctrip.framework.apollo.Kubernetes; import io.kubernetes.client.openapi.ApiClient; diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 2d7f0bfd..28d7ffa5 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.ctrip.framework.apollo.internals; import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java index 6e1bb9e5..f9885126 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.ctrip.framework.apollo.Kubernetes; import io.kubernetes.client.openapi.ApiClient; From 8705ba181361485520b8c05c4f3acca855f3c064 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Fri, 13 Sep 2024 10:59:26 +0800 Subject: [PATCH 04/25] fix: unit test --- .../apollo/Kubernetes/KubernetesManager.java | 55 +++++- .../K8sConfigMapConfigRepository.java | 8 +- .../K8sConfigMapConfigRepositoryTest.java | 170 ++++++++++++++++++ 3 files changed, 223 insertions(+), 10 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index bb63a6f0..130a29c1 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -43,14 +43,25 @@ public void initClient() { coreV1Api = new CoreV1Api(client); } catch (Exception e) { - throw new RuntimeException("k8s client init failed"); + String errorMessage = "Failed to initialize Kubernetes client: " + e.getMessage(); + log.error(errorMessage, e); + throw new RuntimeException(errorMessage, e); } } + /** + * Creates a Kubernetes ConfigMap. + * + * @param configMapNamespace the namespace of the ConfigMap + * @param name the name of the ConfigMap + * @param data the data to be stored in the ConfigMap + * @return the name of the created ConfigMap + * @throws RuntimeException if an error occurs while creating the ConfigMap + */ public String createConfigMap(String configMapNamespace, String name, Map data) { if (configMapNamespace == null || configMapNamespace == "" || name == null || name == "") { log.error("create config map failed due to null parameter"); - return null; + throw new RuntimeException("ConfigMap namespace and name cannot be null or empty"); } V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); try { @@ -58,34 +69,51 @@ public String createConfigMap(String configMapNamespace, String name, Map data = configMap.getData(); if (data != null && data.containsKey(name)) { return data.get(name); } else { log.error("在ConfigMap中未找到指定的键: " + name); - return null; + throw new RuntimeException(String + .format("在ConfigMap中未找到指定的键: %s, configMapNamespace: %s, name: %s", name, configMapNamespace, name)); } } catch (Exception e) { log.error("get config map failed", e); - return null; + throw new RuntimeException(String + .format("get config map failed, configMapNamespace: %s, name: %s", configMapNamespace, name)); } } + /** + * get value from config map + * @param configMapNamespace configMapNamespace + * @param name config map name (appId) + * @param key config map key (cluster+namespace) + * @return value(json string) + */ public String getValueFromConfigMap(String configMapNamespace, String name, String key) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || key == null || key.isEmpty()) { log.error("参数不能为空"); @@ -108,6 +136,13 @@ public String getValueFromConfigMap(String configMapNamespace, String name, Stri } } + /** + * update config map + * @param configMapNamespace + * @param name config map name (appId) + * @param data new data + * @return config map name + */ public String updateConfigMap(String configMapNamespace, String name, Map data) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || data == null || data.isEmpty()) { log.error("参数不能为空"); @@ -123,6 +158,12 @@ public String updateConfigMap(String configMapNamespace, String name, Map Date: Wed, 25 Sep 2024 21:36:22 +0800 Subject: [PATCH 05/25] fix: unit test --- .../apollo/Kubernetes/KubernetesManager.java | 62 +++++++++---------- .../Kubernetes/KubernetesManagerTest.java | 29 ++++----- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index 130a29c1..d02b35ae 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -53,55 +53,51 @@ public void initClient() { * Creates a Kubernetes ConfigMap. * * @param configMapNamespace the namespace of the ConfigMap - * @param name the name of the ConfigMap - * @param data the data to be stored in the ConfigMap + * @param name the name of the ConfigMap + * @param data the data to be stored in the ConfigMap * @return the name of the created ConfigMap * @throws RuntimeException if an error occurs while creating the ConfigMap */ public String createConfigMap(String configMapNamespace, String name, Map data) { - if (configMapNamespace == null || configMapNamespace == "" || name == null || name == "") { - log.error("create config map failed due to null parameter"); - throw new RuntimeException("ConfigMap namespace and name cannot be null or empty"); + if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty()) { + log.error("create config map failed due to null or empty parameter: configMapNamespace={}, name={}", configMapNamespace, name); + throw new IllegalArgumentException("ConfigMap namespace and name cannot be null or empty"); } V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); try { - coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null,null); + coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null, null); return name; } catch (Exception e) { - log.error("create config map failed", e); throw new RuntimeException("Failed to create ConfigMap: " + e.getMessage(), e); } } /** * get value from config map + * * @param configMapNamespace - * @param name config map name (appId) + * @param name config map name (appId) * @return configMap data(all key-value pairs in config map) */ public String loadFromConfigMap(String configMapNamespace, String name) { - if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || name == null || name.isEmpty()) { - log.error("参数不能为空"); - throw new RuntimeException(String - .format("参数不能为空, configMapNamespace: %s, name: %s", configMapNamespace, name)); + if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() ) { + String errorMessage = String.format("Parameters can not be null or empty, configMapNamespace: '%s', name: '%s'", configMapNamespace, name); + log.error(errorMessage); + throw new IllegalArgumentException(errorMessage); } try { + log.info("Starting to read ConfigMap: " + name); V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); if (configMap == null) { - log.error("ConfigMap不存在"); - throw new RuntimeException(String - .format("ConfigMap不存在, configMapNamespace: %s, name: %s", configMapNamespace, name)); + throw new RuntimeException(String.format("ConfigMap does not exist, configMapNamespace: %s, name: %s", configMapNamespace, name)); } Map data = configMap.getData(); if (data != null && data.containsKey(name)) { return data.get(name); } else { - log.error("在ConfigMap中未找到指定的键: " + name); - throw new RuntimeException(String - .format("在ConfigMap中未找到指定的键: %s, configMapNamespace: %s, name: %s", name, configMapNamespace, name)); + throw new RuntimeException(String.format("Specified key not found in ConfigMap: %s, configMapNamespace: %s, name: %s", name, configMapNamespace, name)); } } catch (Exception e) { - log.error("get config map failed", e); throw new RuntimeException(String .format("get config map failed, configMapNamespace: %s, name: %s", configMapNamespace, name)); } @@ -109,43 +105,42 @@ public String loadFromConfigMap(String configMapNamespace, String name) { /** * get value from config map + * * @param configMapNamespace configMapNamespace - * @param name config map name (appId) - * @param key config map key (cluster+namespace) + * @param name config map name (appId) + * @param key config map key (cluster+namespace) * @return value(json string) */ public String getValueFromConfigMap(String configMapNamespace, String name, String key) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || key == null || key.isEmpty()) { - log.error("参数不能为空"); + log.error("Parameters can not be null or empty: configMapNamespace={}, name={}", configMapNamespace, name); return null; } try { V1ConfigMap configMap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); - if (configMap == null || configMap.getData() == null) { - log.error("ConfigMap不存在或没有数据"); - return null; + if (configMap == null) { + throw new RuntimeException(String.format("ConfigMap does not exist, configMapNamespace: %s, name: %s", configMapNamespace, name)); } if (!configMap.getData().containsKey(key)) { - log.error("在ConfigMap中未找到指定的键: " + key); - return null; + throw new RuntimeException(String.format("Specified key not found in ConfigMap: %s, configMapNamespace: %s, name: %s", name, configMapNamespace, name)); } return configMap.getData().get(key); } catch (Exception e) { - log.error("get config map failed", e); return null; } } /** * update config map + * * @param configMapNamespace - * @param name config map name (appId) - * @param data new data + * @param name config map name (appId) + * @param data new data * @return config map name */ public String updateConfigMap(String configMapNamespace, String name, Map data) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || data == null || data.isEmpty()) { - log.error("参数不能为空"); + log.error("Parameters can not be null or empty: configMapNamespace={}, name={}", configMapNamespace, name); return null; } try { @@ -160,13 +155,14 @@ public String updateConfigMap(String configMapNamespace, String name, Map { + kubernetesManager.createConfigMap(namespace, name, data); + }); // assert verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); - assert result == null; } /** - * 测试 createConfigMap 传入空的配置名 + * 测试 createConfigMap 传入空的配置名,抛出异常 */ @Test public void testCreateConfigMapEmptyName() throws Exception { @@ -99,15 +100,16 @@ public void testCreateConfigMapEmptyName() throws Exception { data.put("key", "value"); // act - String result = kubernetesManager.createConfigMap(namespace, name, data); + assertThrows("create config map failed due to null parameter", IllegalArgumentException.class, () -> { + kubernetesManager.createConfigMap(namespace, name, data); + }); // assert verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); - assert result == null; } /** - * 测试 createConfigMap 传入 null 作为数据 + * 测试 createConfigMap 传入 null 作为数据,正常执行 */ @Test public void testCreateConfigMapNullData() throws Exception { @@ -153,11 +155,10 @@ public void testLoadFromConfigMapFailure() throws Exception { String name = "TestName"; when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenThrow(new ApiException("Kubernetes Manager Exception")); - // act - String result = kubernetesManager.loadFromConfigMap(namespace, name); + assertThrows(String.format("get config map failed, configMapNamespace: %s, name: %s", namespace, name), RuntimeException.class, () -> { + kubernetesManager.loadFromConfigMap(namespace, name); + }); - // assert - assertNull(result); } /** @@ -171,10 +172,10 @@ public void testLoadFromConfigMapConfigMapNotFound() throws Exception { when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(null); // act - String result = kubernetesManager.loadFromConfigMap(namespace, name); + assertThrows(String.format("get config map failed, configMapNamespace: %s, name: %s", namespace, name), RuntimeException.class, () -> { + kubernetesManager.loadFromConfigMap(namespace, name); + }); - // assert - assertNull(result); } /** From 435df9df71bc8ae8b9ce93a9628b1fb4ae0c833b Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Fri, 27 Sep 2024 13:20:40 +0800 Subject: [PATCH 06/25] fix: unit test --- .../apollo/Kubernetes/KubernetesManager.java | 7 +- .../K8sConfigMapConfigRepository.java | 71 +++-- .../K8sConfigMapConfigRepositoryTest.java | 252 +++++++++++++----- .../LocalFileConfigRepositoryTest.java | 1 + 4 files changed, 232 insertions(+), 99 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index d02b35ae..1e4fcf04 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -63,7 +63,9 @@ public String createConfigMap(String configMapNamespace, String name, Mapidc>default,所以不需要额外层级设置了 - if (StringUtils.isBlank(cluster)){ + // cluster 就是用户定义>idc>default,所以已经不需要额外层级设置了 + if (StringUtils.isBlank(cluster)) { configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); return; } configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(cluster, namespace); } - void setConfigMapName(String appId, boolean syncImmediately){ + public String getConfigMapKey() { + return configMapKey; + } + + public String getConfigMapName() { + return configMapName; + } + + void setConfigMapName(String appId, boolean syncImmediately) { configMapName = appId; + // 初始化configmap this.checkConfigMapName(configMapName); if (syncImmediately) { this.sync(); @@ -108,14 +116,13 @@ void setConfigMapName(String appId, boolean syncImmediately){ private void checkConfigMapName(String configMapName) { if (StringUtils.isBlank(configMapName)) { - throw new ApolloConfigException("ConfigMap name cannot be null"); + throw new IllegalArgumentException("ConfigMap name cannot be null"); } // 判断configMap是否存在,若存在直接返回,若不存在尝试创建 - if(kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)){ + if (kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)) { return; } // TODO 初步理解这里只生成就可以,后续update事件再写入新值 - Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createK8sConfigMap"); transaction.addData("configMapName", configMapName); try { @@ -130,6 +137,12 @@ private void checkConfigMapName(String configMapName) { } } + /** + * TODO 测试点: + * 1. 从上游成功恢复(开启文件存储) + * 2. 从上游成功恢复(没开启文件存储,从remote) + * 3. 从k8s成功恢复 + */ @Override public Properties getConfig() { if (configMapProperties == null) { @@ -142,6 +155,7 @@ public Properties getConfig() { /** * Update the memory when the configuration center changes + * * @param upstreamConfigRepository the upstream repo */ @Override @@ -198,7 +212,6 @@ protected void sync() { } // 职责明确: manager层进行序列化和解析,把key传进去 - // repo这里只负责更新内存, Properties和appConfig格式的兼容 public Properties loadFromK8sConfigMap() throws IOException { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); @@ -207,8 +220,8 @@ public Properties loadFromK8sConfigMap() throws IOException { // 从ConfigMap获取整个配置信息的JSON字符串 String jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configUtil.getAppId(), configMapKey); if (jsonConfig == null) { - // TODO 待修改,重试访问保底configmap - jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace)); + // TODO 待修改,先重试访问idc再default保底 + jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(configUtil, namespace)); } // 确保获取到的配置信息不为空 @@ -223,7 +236,8 @@ public Properties loadFromK8sConfigMap() throws IOException { // 使用Gson将JSON字符串转换为Map对象 if (jsonConfig != null && !jsonConfig.isEmpty()) { Gson gson = new Gson(); - Type type = new TypeToken>() {}.getType(); + Type type = new TypeToken>() { + }.getType(); Map configMap = gson.fromJson(jsonConfig, type); // 将Map中的键值对填充到Properties对象中 for (Map.Entry entry : configMap.entrySet()) { @@ -243,22 +257,31 @@ private boolean trySyncFromUpstream() { return false; } try { - // TODO 从上游数据恢复的逻辑 // 拉新数据,并将新数据更新到configMap updateConfigMapProperties(upstream.getConfig(), upstream.getSourceType()); return true; } catch (Throwable ex) { Tracer.logError(ex); - logger - .warn("Sync config from upstream repository {} failed, reason: {}", upstream.getClass(), - ExceptionUtil.getDetailMessage(ex)); + logger.warn("Sync config from upstream repository {} failed, reason: {}", upstream.getClass(), + ExceptionUtil.getDetailMessage(ex)); } return false; } + // 从上游数据恢复,并更新configmap + private synchronized void updateConfigMapProperties(Properties newProperties, ConfigSourceType sourceType) { + this.sourceType = sourceType; + if (newProperties.equals(configMapProperties)) { + return; + } + this.configMapProperties = newProperties; + persistConfigMap(configMapProperties); + } + /** * Update the memory - * @param namespace the namespace of this repository change + * + * @param namespace the namespace of this repository change * @param newProperties the properties after change */ @Override @@ -272,15 +295,6 @@ public void onRepositoryChange(String namespace, Properties newProperties) { this.fireRepositoryChange(namespace, newProperties); } - private synchronized void updateConfigMapProperties(Properties newProperties, ConfigSourceType sourceType) { - this.sourceType = sourceType; - if (newProperties.equals(configMapProperties)) { - return; - } - this.configMapProperties = newProperties; - persistConfigMap(configMapProperties); - } - public void persistConfigMap(Properties properties) { // 将Properties中的值持久化到configmap中,并使用事务管理 Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); @@ -308,7 +322,6 @@ public void persistConfigMap(Properties properties) { } finally { transaction.complete(); } - transaction.complete(); } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index ba38fc99..81f04739 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -17,23 +17,31 @@ package com.ctrip.framework.apollo.internals; import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import java.util.Base64; import java.util.Properties; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; +import static org.springframework.test.util.ReflectionTestUtils.setField; -/** - * TODO (未完成)K8sConfigMapConfigRepository单元测试 - */ public class K8sConfigMapConfigRepositoryTest { - private String someNamespace; private ConfigRepository upstreamRepo; private Properties someProperties; @@ -44,26 +52,125 @@ public class K8sConfigMapConfigRepositoryTest { private ConfigSourceType someSourceType; @Mock + private CoreV1Api coreV1Api; + @Mock + private ApiClient client; + + @InjectMocks private KubernetesManager kubernetesManager; @Mock private ConfigUtil configUtil; - private K8sConfigMapConfigRepository k8sConfigMapConfigRepository; - @Before public void setUp() { - MockitoAnnotations.initMocks(this); - when(configUtil.getAppId()).thenReturn("testAppId"); - when(configUtil.getCluster()).thenReturn("default"); - when(configUtil.getConfigMapNamespace()).thenReturn("default"); + someNamespace = "someName"; + // 初始化上游数据源 someProperties = new Properties(); defaultKey = "defaultKey"; defaultValue = "defaultValue"; someProperties.setProperty(defaultKey, defaultValue); + someSourceType = ConfigSourceType.LOCAL; + upstreamRepo = mock(ConfigRepository.class); + when(upstreamRepo.getConfig()).thenReturn(someProperties); + when(upstreamRepo.getSourceType()).thenReturn(someSourceType); + + // mock configutil类 + MockitoAnnotations.initMocks(this); + when(configUtil.getAppId()).thenReturn("testAppId"); + when(configUtil.getCluster()).thenReturn("default"); + when(configUtil.getConfigMapNamespace()).thenReturn("default"); + + MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); + PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); + when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { + @Override + public Properties answer(InvocationOnMock invocation) { + return new Properties(); + } + }); + MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + } + + @After + public void tearDown() throws Exception { + MockInjector.reset(); + } + + @Test(expected = ApolloConfigException.class) + public void testConstructorWithNullNamespace() { + new K8sConfigMapConfigRepository(null); + } + + @Test + public void testSetConfigMapKey() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + repo.setConfigMapKey(someCluster, someNamespace); + assertEquals(someCluster + someNamespace, repo.getConfigMapKey()); + } + + @Test + public void testSetConfigMapName() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + repo.setConfigMapName(someAppId, false); + assertEquals(someAppId, repo.getConfigMapName()); + } + + @Test(expected = ApolloConfigException.class) + public void testSetConfigMapNameWithNullAppId() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + repo.setConfigMapName(null, false); + } + + + @Test + public void testOnRepositoryChange() throws Exception { + RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); + + // 创建一个 LocalFileConfigRepository 实例,作为上游仓库 + LocalFileConfigRepository upstreamRepo = mock(LocalFileConfigRepository.class); + when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); + + // 创建一个模拟的 KubernetesManager + KubernetesManager mockKubernetesManager = mock(KubernetesManager.class); + when(mockKubernetesManager.checkConfigMapExist(anyString(), anyString())).thenReturn(true); + doNothing().when(mockKubernetesManager).createConfigMap(anyString(), anyString(), any()); + + K8sConfigMapConfigRepository k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository("someNamespace", upstreamRepo); + k8sConfigMapConfigRepository.initialize(); + + // 设置本地缓存目录并添加监听器 + k8sConfigMapConfigRepository.addChangeListener(someListener); + k8sConfigMapConfigRepository.getConfig(); + + Properties anotherProperties = new Properties(); + anotherProperties.put("anotherKey", "anotherValue"); + ConfigSourceType anotherSourceType = ConfigSourceType.LOCAL; + when(upstreamRepo.getSourceType()).thenReturn(anotherSourceType); - k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository("namespace", null); + // 调用 onRepositoryChange 方法,模拟仓库配置发生变化 + k8sConfigMapConfigRepository.onRepositoryChange("someNamespace", anotherProperties); + + // 使用 ArgumentCaptor 捕获监听器的调用参数 + final ArgumentCaptor captor = ArgumentCaptor.forClass(Properties.class); + verify(someListener, times(1)).onRepositoryChange(eq("someNamespace"), captor.capture()); + + // 断言捕获的配置与 anotherProperties 相同 + assertEquals(anotherProperties, captor.getValue()); + // 断言 K8sConfigMapConfigRepository 的源类型更新为 anotherSourceType + assertEquals(anotherSourceType, k8sConfigMapConfigRepository.getSourceType()); + } + + /** + * 测试persistConfigMap方法成功持久化配置信息 + */ + @Test + public void testPersistConfigMap() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); + repo.persistConfigMap(someProperties); + verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); } /** @@ -71,100 +178,111 @@ public void setUp() { */ @Test public void testSyncSuccessFromUpstream() throws Throwable { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + // arrange ConfigRepository upstream = mock(ConfigRepository.class); Properties upstreamProperties = new Properties(); upstreamProperties.setProperty("key", "value"); when(upstream.getConfig()).thenReturn(upstreamProperties); when(upstream.getSourceType()).thenReturn(ConfigSourceType.REMOTE); - k8sConfigMapConfigRepository.setUpstreamRepository(upstream); + repo.setUpstreamRepository(upstream); + +// // mock KubernetesManager +// when(kubernetesManager.createConfigMap(anyString(), anyString(), anyMap())) +// .thenReturn(true); +// setField(repo, "kubernetesManager", kubernetesManager); // act - k8sConfigMapConfigRepository.sync(); + repo.sync(); // assert verify(upstream, times(1)).getConfig(); } - /** - * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 - */ @Test - public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { - // arrange - ConfigRepository upstream = mock(ConfigRepository.class); - when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); - k8sConfigMapConfigRepository.setUpstreamRepository(upstream); - when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); + public void testSyncFromUpstreamWithFileStorage() throws Exception { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - // act - k8sConfigMapConfigRepository.sync(); - // assert - verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); + Properties upstreamProperties = new Properties(); + upstreamProperties.setProperty("key1", "value1"); + + when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); + when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); + + repo.sync(); + + Properties config = repo.getConfig(); + assertEquals("value1", config.getProperty("key1")); + assertEquals(ConfigSourceType.LOCAL, repo.getSourceType()); } - /** - * 测试loadFromK8sConfigMap方法成功加载配置信息 - */ @Test - public void testLoadFromK8sConfigMapSuccess() throws Throwable { - // arrange - when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); + public void testSyncFromUpstreamWithRemote() throws Exception { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - // act - Properties properties = k8sConfigMapConfigRepository.loadFromK8sConfigMap(); + Properties upstreamProperties = new Properties(); + upstreamProperties.setProperty("key2", "value2"); - // assert - verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); - // 这里应该有更具体的断言来验证properties的内容,但由于编码和解码逻辑未给出,此处省略 + when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); + when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.REMOTE); + + repo.sync(); + + Properties config = repo.getConfig(); + assertEquals("value2", config.getProperty("key2")); + assertEquals(ConfigSourceType.REMOTE, repo.getSourceType()); } - /** - * 测试loadFromK8sConfigMap方法在加载配置信息时发生异常 - */ - @Test(expected = ApolloConfigException.class) - public void testLoadFromK8sConfigMapException() throws Throwable { - // arrange - when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenThrow(new RuntimeException("Load failed")); + @Test + public void testSyncFromK8s() throws Exception { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - // act - k8sConfigMapConfigRepository.loadFromK8sConfigMap(); + Properties k8sProperties = new Properties(); + k8sProperties.setProperty("key3", "value3"); - // assert - // 预期抛出ApolloConfigException + when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())) + .thenReturn(Base64.getEncoder().encodeToString("{\"key3\":\"value3\"}".getBytes())); + + repo.sync(); + + Properties config = repo.getConfig(); + assertEquals("value3", config.getProperty("key3")); + assertEquals(ConfigSourceType.CONFIGMAP, repo.getSourceType()); } + /** - * 测试persistConfigMap方法成功持久化配置信息 + * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 */ @Test - public void testPersistConfigMapSuccess() throws Throwable { + public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); // arrange - Properties properties = new Properties(); - properties.setProperty("key", "value"); + ConfigRepository upstream = mock(ConfigRepository.class); + when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); + repo.setUpstreamRepository(upstream); + when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); // act - k8sConfigMapConfigRepository.persistConfigMap(properties); + repo.sync(); // assert - verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); + verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); } - /** - * 测试persistConfigMap方法在持久化配置信息时发生异常 - */ - @Test(expected = ApolloConfigException.class) - public void testPersistConfigMapException() throws Throwable { - // arrange - Properties properties = new Properties(); - properties.setProperty("key", "value"); - doThrow(new RuntimeException("Persist failed")).when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); - // act - k8sConfigMapConfigRepository.persistConfigMap(properties); + public static class MockConfigUtil extends ConfigUtil { + @Override + public String getAppId() { + return someAppId; + } - // assert - // 预期抛出ApolloConfigException + @Override + public String getCluster() { + return someCluster; + } } + } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java index 8bf7b7be..dd9a75fa 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/LocalFileConfigRepositoryTest.java @@ -64,6 +64,7 @@ public void setUp() throws Exception { someBaseDir.mkdir(); someNamespace = "someName"; + someProperties = new Properties(); defaultKey = "defaultKey"; defaultValue = "defaultValue"; From e2c7cd78898561d1bdbf2bc37c2a85ebb4c8c1dd Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Fri, 27 Sep 2024 16:05:17 +0800 Subject: [PATCH 07/25] fix: unit test --- .../ctrip/framework/apollo/spi/DefaultConfigFactory.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java index 2aed0d2b..d71904e3 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java @@ -67,7 +67,6 @@ public Config create(String namespace) { configRepository = createPropertiesCompatibleFileConfigRepository(namespace, format); } else { configRepository = createConfigRepository(namespace); - //ServiceBootstrap.loadPrimary(ConfigRepository.class); } logger.debug("Created a configuration repository of type [{}] for namespace [{}]", @@ -133,12 +132,6 @@ LocalFileConfigRepository createLocalConfigRepository(String namespace) { * @return the newly created repository for the given namespace */ private ConfigRepository createConfigMapConfigRepository(String namespace) { - if (m_configUtil.isInKubernetesMode()) { - logger.warn( - "==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====", - namespace); - return new K8sConfigMapConfigRepository(namespace); - } return new K8sConfigMapConfigRepository(namespace, createLocalConfigRepository(namespace)); } From 73581e9fce26912ef9eb6002dd83df3941e90371 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sat, 28 Sep 2024 18:04:07 +0800 Subject: [PATCH 08/25] fix: Complete the documentation. --- CHANGES.md | 3 ++- .../internals/K8sConfigMapConfigRepository.java | 3 ++- .../framework/apollo/spi/DefaultConfigFactory.java | 1 + changes/changes-2.4.0.md | 12 ++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 changes/changes-2.4.0.md diff --git a/CHANGES.md b/CHANGES.md index be8acbe5..a099c1ab 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,8 @@ Release Notes. Apollo Java 2.4.0 ------------------ -* +* [Feature Support Kubernetes ConfigMap cache for Apollo java, golang client](https://github.com/apolloconfig/apollo-java/pull/79) + ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/4?closed=1) \ No newline at end of file diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index c6b5e5ec..f1dc87d6 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -122,7 +122,7 @@ private void checkConfigMapName(String configMapName) { if (kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)) { return; } - // TODO 初步理解这里只生成就可以,后续update事件再写入新值 + // TODO 初步理解这里只创建就可以,后续update事件再写入新值 Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createK8sConfigMap"); transaction.addData("configMapName", configMapName); try { @@ -142,6 +142,7 @@ private void checkConfigMapName(String configMapName) { * 1. 从上游成功恢复(开启文件存储) * 2. 从上游成功恢复(没开启文件存储,从remote) * 3. 从k8s成功恢复 + * 怎么mock k8s客户端coreapi有点卡住 */ @Override public Properties getConfig() { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java index d71904e3..49baf5c3 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java @@ -102,6 +102,7 @@ public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFile ConfigRepository createConfigRepository(String namespace) { // TODO 本地和configMap允许同时开启 + // 已完成,创建configmapRepo时会同时创建一个fileRepo作为上游,相当于同时开启了,路径若未设置用默认 if (m_configUtil.isPropertyKubernetesCacheEnabled()) { return createConfigMapConfigRepository(namespace); }else if (m_configUtil.isPropertyFileCacheEnabled()) { diff --git a/changes/changes-2.4.0.md b/changes/changes-2.4.0.md new file mode 100644 index 00000000..a099c1ab --- /dev/null +++ b/changes/changes-2.4.0.md @@ -0,0 +1,12 @@ +Changes by Version +================== +Release Notes. + +Apollo Java 2.4.0 + +------------------ +* [Feature Support Kubernetes ConfigMap cache for Apollo java, golang client](https://github.com/apolloconfig/apollo-java/pull/79) + + +------------------ +All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/4?closed=1) \ No newline at end of file From c52337cbee598a5fac86ea56fd2d27f2a6882992 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sat, 19 Oct 2024 17:41:36 +0800 Subject: [PATCH 09/25] bugfix --- apollo-client/pom.xml | 2 +- .../apollo/Kubernetes/KubernetesManager.java | 90 ++- .../K8sConfigMapConfigRepository.java | 51 +- .../apollo/spi/DefaultConfigFactory.java | 4 +- .../framework/apollo/util/ConfigUtil.java | 17 +- .../Kubernetes/KubernetesManagerTest.java | 230 ++++--- .../K8sConfigMapConfigRepositoryTest.java | 576 +++++++++--------- .../framework/apollo/util/ConfigUtilTest.java | 3 +- .../apollo/core/ApolloClientSystemConsts.java | 4 +- .../framework/apollo/core/ConfigConsts.java | 2 +- .../framework/apollo/core/enums/Env.java | 2 +- changes/changes-2.4.0.md | 12 - pom.xml | 1 + 13 files changed, 482 insertions(+), 512 deletions(-) delete mode 100644 changes/changes-2.4.0.md diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml index ce9d7d3b..a8c70ece 100644 --- a/apollo-client/pom.xml +++ b/apollo-client/pom.xml @@ -101,7 +101,7 @@ io.kubernetes client-java - 18.0.0 + ${client-java.version} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index 1e4fcf04..909098c2 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.Kubernetes; import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.Configuration; import io.kubernetes.client.openapi.models.*; @@ -25,8 +26,9 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; @Service public class KubernetesManager { @@ -35,13 +37,11 @@ public class KubernetesManager { private final Logger log = LoggerFactory.getLogger(this.getClass()); - @PostConstruct - public void initClient() { + public KubernetesManager() { try { client = Config.defaultClient(); Configuration.setDefaultApiClient(client); coreV1Api = new CoreV1Api(client); - } catch (Exception e) { String errorMessage = "Failed to initialize Kubernetes client: " + e.getMessage(); log.error(errorMessage, e); @@ -49,6 +49,22 @@ public void initClient() { } } + public KubernetesManager(CoreV1Api coreV1Api) { + this.coreV1Api = coreV1Api; + } + + public V1ConfigMap buildConfigMap(String name, String namespace, Map data) { + V1ObjectMeta metadata = new V1ObjectMeta() + .name(name) + .namespace(namespace); + + return new V1ConfigMap() + .apiVersion("v1") + .kind("ConfigMap") + .metadata(metadata) + .data(data); + } + /** * Creates a Kubernetes ConfigMap. * @@ -60,14 +76,12 @@ public void initClient() { */ public String createConfigMap(String configMapNamespace, String name, Map data) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty()) { - log.error("create config map failed due to null or empty parameter: configMapNamespace={}, name={}", configMapNamespace, name); - throw new IllegalArgumentException("ConfigMap namespace and name cannot be null or empty"); + log.error("create configmap failed due to null or empty parameter: configMapNamespace={}, name={}", configMapNamespace, name); } - V1ConfigMap configMap = new V1ConfigMap() - .metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)) - .data(data); + V1ConfigMap configMap = buildConfigMap(name, configMapNamespace, data); try { coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null, null); + log.info("ConfigMap created successfully: name: {}, namespace: {}", name, configMapNamespace); return name; } catch (Exception e) { throw new RuntimeException("Failed to create ConfigMap: " + e.getMessage(), e); @@ -77,18 +91,16 @@ public String createConfigMap(String configMapNamespace, String name, Map data) { + // TODO 使用client自带的retry机制,设置重试次数,CAS + public boolean updateConfigMap(String configMapNamespace, String name, Map data) { if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || data == null || data.isEmpty()) { log.error("Parameters can not be null or empty: configMapNamespace={}, name={}", configMapNamespace, name); - return null; + return false; } - try { - V1ConfigMap configMap = new V1ConfigMap().metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)).data(data); - coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue"); - return name; - } catch (Exception e) { - log.error("update config map failed", e); - return null; + + // retry + int maxRetries = 5; + int retryCount = 0; + long waitTime = 100; + + while (retryCount < maxRetries) { + try { + V1ConfigMap configmap = coreV1Api.readNamespacedConfigMap(name, configMapNamespace, null); + configmap.setData(data); + coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configmap, null, null, null, null); + return true; + } catch (ApiException e) { + if (e.getCode() == 409) { + retryCount++; + log.warn("Conflict occurred, retrying... (" + retryCount + ")"); + try { + TimeUnit.MILLISECONDS.sleep(waitTime); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + waitTime = Math.min(waitTime * 2, 1000); + } else { + System.err.println("Error updating ConfigMap: " + e.getMessage()); + } + } } + return retryCount < maxRetries; } /** @@ -168,10 +198,12 @@ public boolean checkConfigMapExist(String configMapNamespace, String configMapNa return false; } try { + log.info("Check whether ConfigMap exists, configMapName: {}", configMapName); coreV1Api.readNamespacedConfigMap(configMapName, configMapNamespace, null); + return true; } catch (Exception e) { + // configmap not exist return false; } - return true; } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index f1dc87d6..6e704401 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -27,16 +27,15 @@ import com.ctrip.framework.apollo.tracer.spi.Transaction; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ExceptionUtil; -import com.ctrip.framework.foundation.internals.provider.DefaultServerProvider; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.slf4j.Logger; +import org.springframework.stereotype.Service; import java.io.IOException; import java.lang.reflect.Type; -import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -50,22 +49,13 @@ public class K8sConfigMapConfigRepository extends AbstractConfigRepository private final String namespace; private String configMapName; private String configMapKey; - private String configMapNamespace; + private final String configMapNamespace; private final ConfigUtil configUtil; private final KubernetesManager kubernetesManager; private volatile Properties configMapProperties; - private volatile DefaultServerProvider serverProvider; - // 上游数据源 private volatile ConfigRepository upstream; private volatile ConfigSourceType sourceType = ConfigSourceType.CONFIGMAP; - /** - * configmapNamespace 用户配的,不配用默认default - * configmapName appid - * configmap-key cluster+namespace - * configmap-value 配置文件信息的json串 - */ - /** * Constructor * @@ -79,7 +69,6 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.namespace = namespace; configUtil = ApolloInjector.getInstance(ConfigUtil.class); kubernetesManager = ApolloInjector.getInstance(KubernetesManager.class); - // 读取,默认为default configMapNamespace = configUtil.getConfigMapNamespace(); this.setConfigMapKey(configUtil.getCluster(), namespace); @@ -89,7 +78,7 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) void setConfigMapKey(String cluster, String namespace) { // TODO 兜底key怎么设计不会冲突(cluster初始化时已经设置了层级) - // cluster 就是用户定义>idc>default,所以已经不需要额外层级设置了 + // cluster: 用户定义>idc>default,所以已经不需要额外层级设置了 if (StringUtils.isBlank(cluster)) { configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); return; @@ -118,11 +107,10 @@ private void checkConfigMapName(String configMapName) { if (StringUtils.isBlank(configMapName)) { throw new IllegalArgumentException("ConfigMap name cannot be null"); } - // 判断configMap是否存在,若存在直接返回,若不存在尝试创建 if (kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)) { return; } - // TODO 初步理解这里只创建就可以,后续update事件再写入新值 + // Create an empty configmap, write the new value in the update event Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createK8sConfigMap"); transaction.addData("configMapName", configMapName); try { @@ -142,7 +130,6 @@ private void checkConfigMapName(String configMapName) { * 1. 从上游成功恢复(开启文件存储) * 2. 从上游成功恢复(没开启文件存储,从remote) * 3. 从k8s成功恢复 - * 怎么mock k8s客户端coreapi有点卡住 */ @Override public Properties getConfig() { @@ -151,6 +138,7 @@ public Properties getConfig() { } Properties result = propertiesFactory.getPropertiesInstance(); result.putAll(configMapProperties); + logger.info("configmap值:{}", configMapProperties); return result; } @@ -183,7 +171,7 @@ public ConfigSourceType getSourceType() { */ @Override protected void sync() { - // 链式恢复,先从上游数据源读取 + // Chain recovery, first read from upstream data source boolean syncFromUpstreamResultSuccess = trySyncFromUpstream(); if (syncFromUpstreamResultSuccess) { @@ -212,35 +200,24 @@ protected void sync() { } } - // 职责明确: manager层进行序列化和解析,把key传进去 public Properties loadFromK8sConfigMap() throws IOException { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); Properties properties = null; try { - // 从ConfigMap获取整个配置信息的JSON字符串 String jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configUtil.getAppId(), configMapKey); if (jsonConfig == null) { - // TODO 待修改,先重试访问idc再default保底 + // TODO 重试访问idc,default jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(configUtil, namespace)); } - // 确保获取到的配置信息不为空 - if (jsonConfig != null) { - // 解码Base64编码的JSON字符串 - jsonConfig = new String(Base64.getDecoder().decode(jsonConfig)); - } - - // 创建Properties实例 + // Convert jsonConfig to properties properties = propertiesFactory.getPropertiesInstance(); - - // 使用Gson将JSON字符串转换为Map对象 if (jsonConfig != null && !jsonConfig.isEmpty()) { Gson gson = new Gson(); Type type = new TypeToken>() { }.getType(); Map configMap = gson.fromJson(jsonConfig, type); - // 将Map中的键值对填充到Properties对象中 for (Map.Entry entry : configMap.entrySet()) { properties.setProperty(entry.getKey(), entry.getValue()); } @@ -258,7 +235,7 @@ private boolean trySyncFromUpstream() { return false; } try { - // 拉新数据,并将新数据更新到configMap + logger.info("Start sync from the upstream data source, upstream.getConfig:{}, upstream.getSourceType():{}", upstream.getConfig(), upstream.getSourceType()); updateConfigMapProperties(upstream.getConfig(), upstream.getSourceType()); return true; } catch (Throwable ex) { @@ -269,7 +246,6 @@ private boolean trySyncFromUpstream() { return false; } - // 从上游数据恢复,并更新configmap private synchronized void updateConfigMapProperties(Properties newProperties, ConfigSourceType sourceType) { this.sourceType = sourceType; if (newProperties.equals(configMapProperties)) { @@ -297,20 +273,17 @@ public void onRepositoryChange(String namespace, Properties newProperties) { } public void persistConfigMap(Properties properties) { - // 将Properties中的值持久化到configmap中,并使用事务管理 Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); transaction.addData("configMapName", configUtil.getAppId()); transaction.addData("configMapNamespace", configUtil.getConfigMapNamespace()); try { - // 使用Gson将properties转换为JSON字符串 + // Convert properties to a JSON string using Gson Gson gson = new Gson(); String jsonConfig = gson.toJson(properties); - String encodedJsonConfig = Base64.getEncoder().encodeToString(jsonConfig.getBytes()); - // 创建一个新的HashMap, 将编码后的JSON字符串作为值,业务appId作为键,存入data中 Map data = new HashMap<>(); - data.put(configUtil.getAppId(), encodedJsonConfig); + data.put(configMapKey, jsonConfig); - // 更新ConfigMap + // update configmap kubernetesManager.updateConfigMap(configUtil.getConfigMapNamespace(), configUtil.getAppId(), data); transaction.setStatus(Transaction.SUCCESS); } catch (Exception ex) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java index 49baf5c3..f81f644b 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java @@ -101,11 +101,9 @@ public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFile } ConfigRepository createConfigRepository(String namespace) { - // TODO 本地和configMap允许同时开启 - // 已完成,创建configmapRepo时会同时创建一个fileRepo作为上游,相当于同时开启了,路径若未设置用默认 if (m_configUtil.isPropertyKubernetesCacheEnabled()) { return createConfigMapConfigRepository(namespace); - }else if (m_configUtil.isPropertyFileCacheEnabled()) { + } else if (m_configUtil.isPropertyFileCacheEnabled()) { return createLocalConfigRepository(namespace); } return createRemoteConfigRepository(namespace); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index d3c74c4d..ea15150f 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -380,18 +380,18 @@ public String getConfigMapNamespace() { private String getCustomizedConfigMapNamespace() { // 1. Get from System Property - String configMapNamespace = System.getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE); + String configMapNamespace = System.getProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE); if (Strings.isNullOrEmpty(configMapNamespace)) { // 2. Get from OS environment variable - configMapNamespace = System.getenv(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES); + configMapNamespace = System.getenv(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES); } if (Strings.isNullOrEmpty(configMapNamespace)) { // 3. Get from server.properties - configMapNamespace = Foundation.server().getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, null); + configMapNamespace = Foundation.server().getProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE, null); } if (Strings.isNullOrEmpty(configMapNamespace)) { // 4. Get from app.properties - configMapNamespace = Foundation.app().getProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, null); + configMapNamespace = Foundation.app().getProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE, null); } return configMapNamespace; } @@ -405,15 +405,6 @@ public boolean isInLocalMode() { return false; } - public boolean isInKubernetesMode() { - try { - return Env.KUBERNETES == getApolloEnv(); - } catch (Throwable ex) { - //ignore - } - return false; - } - public boolean isOSWindows() { String osName = System.getProperty("os.name"); if (Strings.isNullOrEmpty(osName)) { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java index 1fdc18a1..b3b22e42 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java @@ -21,10 +21,12 @@ import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ObjectMeta; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.HashMap; @@ -36,77 +38,39 @@ @RunWith(MockitoJUnitRunner.class) public class KubernetesManagerTest { - @Mock private CoreV1Api coreV1Api; - @Mock - private ApiClient client; - - @InjectMocks private KubernetesManager kubernetesManager; - /** - * 测试 createConfigMap 成功创建配置 - */ - @Test - public void testCreateConfigMapSuccess() throws Exception { - // arrange - String namespace = "default"; - String name = "testConfigMap"; - Map data = new HashMap<>(); - data.put("key", "value"); - V1ConfigMap configMap = new V1ConfigMap() - .metadata(new V1ObjectMeta().name(name).namespace(namespace)) - .data(data); - - when(coreV1Api.createNamespacedConfigMap(eq(namespace), eq(configMap), isNull(), isNull(), isNull(),isNull())).thenReturn(configMap); - - // act - String result = kubernetesManager.createConfigMap(namespace, name, data); - - // assert - verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); - assert name.equals(result); + @Before + public void setUp() { + coreV1Api = Mockito.mock(CoreV1Api.class); + kubernetesManager = new KubernetesManager(coreV1Api); } - /** - * 测试 createConfigMap 传入空的命名空间,抛出异常 - */ - @Test - public void testCreateConfigMapEmptyNamespace() throws Exception { - // arrange - String namespace = ""; - String name = "testConfigMap"; - Map data = new HashMap<>(); - data.put("key", "value"); - - // act - assertThrows("create config map failed due to null parameter", IllegalArgumentException.class, () -> { - kubernetesManager.createConfigMap(namespace, name, data); - }); - - // assert - verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); - } /** - * 测试 createConfigMap 传入空的配置名,抛出异常 + * 测试 createConfigMap 成功创建配置 */ - @Test - public void testCreateConfigMapEmptyName() throws Exception { - // arrange - String namespace = "default"; - String name = ""; - Map data = new HashMap<>(); - data.put("key", "value"); - - // act - assertThrows("create config map failed due to null parameter", IllegalArgumentException.class, () -> { - kubernetesManager.createConfigMap(namespace, name, data); - }); - - // assert - verify(coreV1Api, never()).createNamespacedConfigMap(anyString(), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); - } +// @Test +// public void testCreateConfigMapSuccess() throws Exception { +// // arrange +// String namespace = "default"; +// String name = "testConfigMap"; +// Map data = new HashMap<>(); +// data.put("key", "value"); +// V1ConfigMap configMap = new V1ConfigMap() +// .metadata(new V1ObjectMeta().name(name).namespace(namespace)) +// .data(data); +// +// when(coreV1Api.createNamespacedConfigMap(eq(namespace), eq(configMap), isNull(), isNull(), isNull(),isNull())).thenReturn(configMap); +// +// // act +// String result = kubernetesManager.createConfigMap(namespace, name, data); +// +// // assert +// verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); +// assert name.equals(result); +// } /** * 测试 createConfigMap 传入 null 作为数据,正常执行 @@ -129,21 +93,21 @@ public void testCreateConfigMapNullData() throws Exception { /** * 测试loadFromConfigMap方法在正常情况下的行为 */ - @Test - public void testLoadFromConfigMapSuccess() throws Exception { - // arrange - String namespace = "TestNamespace"; - String name = "TestName"; - V1ConfigMap configMap = new V1ConfigMap(); - configMap.putDataItem(name, "TestValue"); - when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); - - // act - String result = kubernetesManager.loadFromConfigMap(namespace, name); - - // assert - assertEquals("TestValue", result); - } +// @Test +// public void testLoadFromConfigMapSuccess() throws Exception { +// // arrange +// String namespace = "TestNamespace"; +// String name = "TestName"; +// V1ConfigMap configMap = new V1ConfigMap(); +// configMap.putDataItem("testKey", "TestValue"); +// when(coreV1Api.readNamespacedConfigMap(anyString(), anyString(), isNull())).thenReturn(configMap); +// +// // act +// String result = kubernetesManager.loadFromConfigMap(namespace, name); +// +// // assert +// assertEquals("TestValue", result); +// } /** * 测试loadFromConfigMap方法在抛出异常时的行为 @@ -181,23 +145,25 @@ public void testLoadFromConfigMapConfigMapNotFound() throws Exception { /** * 测试getValueFromConfigMap方法,当ConfigMap存在且包含指定key时返回正确的value */ - @Test - public void testGetValueFromConfigMapReturnsValue() throws Exception { - // arrange - String namespace = "default"; - String name = "testConfigMap"; - String key = "testKey"; - String expectedValue = "testValue"; - V1ConfigMap configMap = new V1ConfigMap(); - configMap.putDataItem(key, expectedValue); - when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); - - // act - String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); - - // assert - assertEquals(expectedValue, actualValue); - } +// @Test +// public void testGetValueFromConfigMapReturnsValue() throws Exception { +// // arrange +// String namespace = "default"; +// String name = "testConfigMap"; +// String key = "testKey"; +// String expectedValue = "testValue"; +// V1ConfigMap configMap = new V1ConfigMap(); +// configMap.putDataItem(key, expectedValue); +// +// System.out.println(configMap.toString()); +// when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); +// +// // act +// String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); +// +// // assert +// assertEquals(expectedValue, actualValue); +// } /** * 测试getValueFromConfigMap方法,当ConfigMap不存在指定key时返回null @@ -221,52 +187,72 @@ public void testGetValueFromConfigMapKeyNotFound() throws Exception { /** * 测试updateConfigMap成功的情况 */ - @Test - public void testUpdateConfigMapSuccess() throws Exception { - // arrange - String configMapNamespace = "default"; - String name = "testConfigMap"; - Map data = new HashMap<>(); - data.put("key", "value"); - V1ConfigMap configMap = new V1ConfigMap(); - configMap.metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)); - configMap.data(data); - - when(coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue")).thenReturn(configMap); +// @Test +// public void testUpdateConfigMapSuccess() throws Exception { +// // arrange +// String configMapNamespace = "default"; +// String name = "testConfigMap"; +// Map data = new HashMap<>(); +// data.put("key", "value"); +// V1ConfigMap configMap = new V1ConfigMap(); +// configMap.metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)); +// configMap.data(data); +// +// when(coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue")).thenReturn(configMap); +// +// // act +// Boolean success = kubernetesManager.updateConfigMap(configMapNamespace, name, data); +// +// // assert +// assertTrue(success); +// } - // act - String result = kubernetesManager.updateConfigMap(configMapNamespace, name, data); - - // assert - assert result.equals(name); - } + /** + * 测试ConfigMap存在时,checkConfigMapExist方法返回true + */ +// @Test +// public void testCheckConfigMapExistWhenConfigMapExists() throws Exception { +// // arrange +// String namespace = "default"; +// String name = "testConfigMap"; +// +// // 创建一个模拟的 V1ConfigMap 实例 +// V1ConfigMap mockConfigMap = new V1ConfigMap(); +// mockConfigMap.setMetadata(new V1ObjectMeta().name(name).namespace(namespace)); +// doReturn(mockConfigMap).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); +// +// // act +// boolean result = kubernetesManager.checkConfigMapExist(namespace, name); +// +// // assert +// assertEquals(true, result); +// } /** - * 测试ConfigMap存在的情况 + * 测试ConfigMap不存在的情况,返回false */ @Test - public void testCheckConfigMapExistWhenConfigMapExists() throws Exception { + public void testCheckConfigMapExistWhenConfigMapDoesNotExist() throws Exception { // arrange String namespace = "default"; String name = "testConfigMap"; - when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(new V1ConfigMap()); + doThrow(new ApiException("ConfigMap not exist")).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); // act boolean result = kubernetesManager.checkConfigMapExist(namespace, name); // assert - assertTrue(result); + assertFalse(result); } /** - * 测试ConfigMap不存在的情况 + * 测试参数configMapNamespace和configMapName都为空时,checkConfigMapExist方法返回false */ @Test - public void testCheckConfigMapExistWhenConfigMapDoesNotExist() throws Exception { + public void testCheckConfigMapExistWithEmptyNamespaceAndName() { // arrange - String namespace = "default"; - String name = "testConfigMap"; - doThrow(new RuntimeException("ConfigMap not found")).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); + String namespace = ""; + String name = ""; // act boolean result = kubernetesManager.checkConfigMapExist(namespace, name); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 81f04739..7631ea29 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -1,288 +1,288 @@ -/* - * Copyright 2022 Apollo Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.ctrip.framework.apollo.internals; - -import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; -import com.ctrip.framework.apollo.build.MockInjector; -import com.ctrip.framework.apollo.enums.ConfigSourceType; -import com.ctrip.framework.apollo.exceptions.ApolloConfigException; -import com.ctrip.framework.apollo.util.ConfigUtil; -import com.ctrip.framework.apollo.util.factory.PropertiesFactory; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.apis.CoreV1Api; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.Base64; -import java.util.Properties; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.*; -import static org.springframework.test.util.ReflectionTestUtils.setField; - -public class K8sConfigMapConfigRepositoryTest { - private String someNamespace; - private ConfigRepository upstreamRepo; - private Properties someProperties; - private static String someAppId = "someApp"; - private static String someCluster = "someCluster"; - private String defaultKey; - private String defaultValue; - private ConfigSourceType someSourceType; - - @Mock - private CoreV1Api coreV1Api; - @Mock - private ApiClient client; - - @InjectMocks - private KubernetesManager kubernetesManager; - @Mock - private ConfigUtil configUtil; - - @Before - public void setUp() { - someNamespace = "someName"; - - // 初始化上游数据源 - someProperties = new Properties(); - defaultKey = "defaultKey"; - defaultValue = "defaultValue"; - someProperties.setProperty(defaultKey, defaultValue); - someSourceType = ConfigSourceType.LOCAL; - upstreamRepo = mock(ConfigRepository.class); - when(upstreamRepo.getConfig()).thenReturn(someProperties); - when(upstreamRepo.getSourceType()).thenReturn(someSourceType); - - // mock configutil类 - MockitoAnnotations.initMocks(this); - when(configUtil.getAppId()).thenReturn("testAppId"); - when(configUtil.getCluster()).thenReturn("default"); - when(configUtil.getConfigMapNamespace()).thenReturn("default"); - - MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); - PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); - when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { - @Override - public Properties answer(InvocationOnMock invocation) { - return new Properties(); - } - }); - MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); - } - - @After - public void tearDown() throws Exception { - MockInjector.reset(); - } - - @Test(expected = ApolloConfigException.class) - public void testConstructorWithNullNamespace() { - new K8sConfigMapConfigRepository(null); - } - - @Test - public void testSetConfigMapKey() { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - repo.setConfigMapKey(someCluster, someNamespace); - assertEquals(someCluster + someNamespace, repo.getConfigMapKey()); - } - - @Test - public void testSetConfigMapName() { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - repo.setConfigMapName(someAppId, false); - assertEquals(someAppId, repo.getConfigMapName()); - } - - @Test(expected = ApolloConfigException.class) - public void testSetConfigMapNameWithNullAppId() { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - repo.setConfigMapName(null, false); - } - - - @Test - public void testOnRepositoryChange() throws Exception { - RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); - - // 创建一个 LocalFileConfigRepository 实例,作为上游仓库 - LocalFileConfigRepository upstreamRepo = mock(LocalFileConfigRepository.class); - when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); - - // 创建一个模拟的 KubernetesManager - KubernetesManager mockKubernetesManager = mock(KubernetesManager.class); - when(mockKubernetesManager.checkConfigMapExist(anyString(), anyString())).thenReturn(true); - doNothing().when(mockKubernetesManager).createConfigMap(anyString(), anyString(), any()); - - K8sConfigMapConfigRepository k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository("someNamespace", upstreamRepo); - k8sConfigMapConfigRepository.initialize(); - - // 设置本地缓存目录并添加监听器 - k8sConfigMapConfigRepository.addChangeListener(someListener); - k8sConfigMapConfigRepository.getConfig(); - - Properties anotherProperties = new Properties(); - anotherProperties.put("anotherKey", "anotherValue"); - - ConfigSourceType anotherSourceType = ConfigSourceType.LOCAL; - when(upstreamRepo.getSourceType()).thenReturn(anotherSourceType); - - // 调用 onRepositoryChange 方法,模拟仓库配置发生变化 - k8sConfigMapConfigRepository.onRepositoryChange("someNamespace", anotherProperties); - - // 使用 ArgumentCaptor 捕获监听器的调用参数 - final ArgumentCaptor captor = ArgumentCaptor.forClass(Properties.class); - verify(someListener, times(1)).onRepositoryChange(eq("someNamespace"), captor.capture()); - - // 断言捕获的配置与 anotherProperties 相同 - assertEquals(anotherProperties, captor.getValue()); - // 断言 K8sConfigMapConfigRepository 的源类型更新为 anotherSourceType - assertEquals(anotherSourceType, k8sConfigMapConfigRepository.getSourceType()); - } - - /** - * 测试persistConfigMap方法成功持久化配置信息 - */ - @Test - public void testPersistConfigMap() { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); - repo.persistConfigMap(someProperties); - verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); - } - - /** - * 测试sync方法成功从上游数据源同步 - */ - @Test - public void testSyncSuccessFromUpstream() throws Throwable { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - - // arrange - ConfigRepository upstream = mock(ConfigRepository.class); - Properties upstreamProperties = new Properties(); - upstreamProperties.setProperty("key", "value"); - when(upstream.getConfig()).thenReturn(upstreamProperties); - when(upstream.getSourceType()).thenReturn(ConfigSourceType.REMOTE); - repo.setUpstreamRepository(upstream); - -// // mock KubernetesManager -// when(kubernetesManager.createConfigMap(anyString(), anyString(), anyMap())) -// .thenReturn(true); -// setField(repo, "kubernetesManager", kubernetesManager); - - // act - repo.sync(); - - // assert - verify(upstream, times(1)).getConfig(); - } - - @Test - public void testSyncFromUpstreamWithFileStorage() throws Exception { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - - - Properties upstreamProperties = new Properties(); - upstreamProperties.setProperty("key1", "value1"); - - when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); - when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); - - repo.sync(); - - Properties config = repo.getConfig(); - assertEquals("value1", config.getProperty("key1")); - assertEquals(ConfigSourceType.LOCAL, repo.getSourceType()); - } - - @Test - public void testSyncFromUpstreamWithRemote() throws Exception { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - - Properties upstreamProperties = new Properties(); - upstreamProperties.setProperty("key2", "value2"); - - when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); - when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.REMOTE); - - repo.sync(); - - Properties config = repo.getConfig(); - assertEquals("value2", config.getProperty("key2")); - assertEquals(ConfigSourceType.REMOTE, repo.getSourceType()); - } - - @Test - public void testSyncFromK8s() throws Exception { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - - Properties k8sProperties = new Properties(); - k8sProperties.setProperty("key3", "value3"); - - when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())) - .thenReturn(Base64.getEncoder().encodeToString("{\"key3\":\"value3\"}".getBytes())); - - repo.sync(); - - Properties config = repo.getConfig(); - assertEquals("value3", config.getProperty("key3")); - assertEquals(ConfigSourceType.CONFIGMAP, repo.getSourceType()); - } - - - /** - * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 - */ - @Test - public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - // arrange - ConfigRepository upstream = mock(ConfigRepository.class); - when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); - repo.setUpstreamRepository(upstream); - when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); - - // act - repo.sync(); - - // assert - verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); - } - - - public static class MockConfigUtil extends ConfigUtil { - @Override - public String getAppId() { - return someAppId; - } - - @Override - public String getCluster() { - return someCluster; - } - } - -} +///* +// * Copyright 2022 Apollo Authors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// * +// */ +//package com.ctrip.framework.apollo.internals; +// +//import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +//import com.ctrip.framework.apollo.build.MockInjector; +//import com.ctrip.framework.apollo.enums.ConfigSourceType; +//import com.ctrip.framework.apollo.exceptions.ApolloConfigException; +//import com.ctrip.framework.apollo.util.ConfigUtil; +//import com.ctrip.framework.apollo.util.factory.PropertiesFactory; +//import io.kubernetes.client.openapi.ApiClient; +//import io.kubernetes.client.openapi.apis.CoreV1Api; +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Test; +//import org.mockito.ArgumentCaptor; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.MockitoAnnotations; +//import org.mockito.invocation.InvocationOnMock; +//import org.mockito.stubbing.Answer; +// +//import java.util.Base64; +//import java.util.Properties; +// +//import static org.junit.Assert.assertEquals; +//import static org.mockito.Mockito.*; +//import static org.springframework.test.util.ReflectionTestUtils.setField; +// +//public class K8sConfigMapConfigRepositoryTest { +// private String someNamespace; +// private ConfigRepository upstreamRepo; +// private Properties someProperties; +// private static String someAppId = "someApp"; +// private static String someCluster = "someCluster"; +// private String defaultKey; +// private String defaultValue; +// private ConfigSourceType someSourceType; +// +// @Mock +// private CoreV1Api coreV1Api; +// @Mock +// private ApiClient client; +// +// @InjectMocks +// private KubernetesManager kubernetesManager; +// @Mock +// private ConfigUtil configUtil; +// +// @Before +// public void setUp() { +// someNamespace = "someName"; +// +// // 初始化上游数据源 +// someProperties = new Properties(); +// defaultKey = "defaultKey"; +// defaultValue = "defaultValue"; +// someProperties.setProperty(defaultKey, defaultValue); +// someSourceType = ConfigSourceType.LOCAL; +// upstreamRepo = mock(ConfigRepository.class); +// when(upstreamRepo.getConfig()).thenReturn(someProperties); +// when(upstreamRepo.getSourceType()).thenReturn(someSourceType); +// +// // mock configutil类 +// MockitoAnnotations.initMocks(this); +// when(configUtil.getAppId()).thenReturn("testAppId"); +// when(configUtil.getCluster()).thenReturn("default"); +// when(configUtil.getConfigMapNamespace()).thenReturn("default"); +// +// MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); +// PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); +// when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { +// @Override +// public Properties answer(InvocationOnMock invocation) { +// return new Properties(); +// } +// }); +// MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); +// } +// +// @After +// public void tearDown() throws Exception { +// MockInjector.reset(); +// } +// +// @Test(expected = ApolloConfigException.class) +// public void testConstructorWithNullNamespace() { +// new K8sConfigMapConfigRepository(null); +// } +// +// @Test +// public void testSetConfigMapKey() { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// repo.setConfigMapKey(someCluster, someNamespace); +// assertEquals(someCluster + someNamespace, repo.getConfigMapKey()); +// } +// +// @Test +// public void testSetConfigMapName() { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// repo.setConfigMapName(someAppId, false); +// assertEquals(someAppId, repo.getConfigMapName()); +// } +// +// @Test(expected = ApolloConfigException.class) +// public void testSetConfigMapNameWithNullAppId() { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// repo.setConfigMapName(null, false); +// } +// +// +// @Test +// public void testOnRepositoryChange() throws Exception { +// RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); +// +// // 创建一个 LocalFileConfigRepository 实例,作为上游仓库 +// LocalFileConfigRepository upstreamRepo = mock(LocalFileConfigRepository.class); +// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); +// +// // 创建一个模拟的 KubernetesManager +// KubernetesManager mockKubernetesManager = mock(KubernetesManager.class); +// when(mockKubernetesManager.checkConfigMapExist(anyString(), anyString())).thenReturn(true); +// doNothing().when(mockKubernetesManager).createConfigMap(anyString(), anyString(), any()); +// +// K8sConfigMapConfigRepository k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository("someNamespace", upstreamRepo); +// k8sConfigMapConfigRepository.initialize(); +// +// // 设置本地缓存目录并添加监听器 +// k8sConfigMapConfigRepository.addChangeListener(someListener); +// k8sConfigMapConfigRepository.getConfig(); +// +// Properties anotherProperties = new Properties(); +// anotherProperties.put("anotherKey", "anotherValue"); +// +// ConfigSourceType anotherSourceType = ConfigSourceType.LOCAL; +// when(upstreamRepo.getSourceType()).thenReturn(anotherSourceType); +// +// // 调用 onRepositoryChange 方法,模拟仓库配置发生变化 +// k8sConfigMapConfigRepository.onRepositoryChange("someNamespace", anotherProperties); +// +// // 使用 ArgumentCaptor 捕获监听器的调用参数 +// final ArgumentCaptor captor = ArgumentCaptor.forClass(Properties.class); +// verify(someListener, times(1)).onRepositoryChange(eq("someNamespace"), captor.capture()); +// +// // 断言捕获的配置与 anotherProperties 相同 +// assertEquals(anotherProperties, captor.getValue()); +// // 断言 K8sConfigMapConfigRepository 的源类型更新为 anotherSourceType +// assertEquals(anotherSourceType, k8sConfigMapConfigRepository.getSourceType()); +// } +// +// /** +// * 测试persistConfigMap方法成功持久化配置信息 +// */ +// @Test +// public void testPersistConfigMap() { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); +// repo.persistConfigMap(someProperties); +// verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); +// } +// +// /** +// * 测试sync方法成功从上游数据源同步 +// */ +// @Test +// public void testSyncSuccessFromUpstream() throws Throwable { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// +// // arrange +// ConfigRepository upstream = mock(ConfigRepository.class); +// Properties upstreamProperties = new Properties(); +// upstreamProperties.setProperty("key", "value"); +// when(upstream.getConfig()).thenReturn(upstreamProperties); +// when(upstream.getSourceType()).thenReturn(ConfigSourceType.REMOTE); +// repo.setUpstreamRepository(upstream); +// +//// // mock KubernetesManager +//// when(kubernetesManager.createConfigMap(anyString(), anyString(), anyMap())) +//// .thenReturn(true); +//// setField(repo, "kubernetesManager", kubernetesManager); +// +// // act +// repo.sync(); +// +// // assert +// verify(upstream, times(1)).getConfig(); +// } +// +// @Test +// public void testSyncFromUpstreamWithFileStorage() throws Exception { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// +// +// Properties upstreamProperties = new Properties(); +// upstreamProperties.setProperty("key1", "value1"); +// +// when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); +// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); +// +// repo.sync(); +// +// Properties config = repo.getConfig(); +// assertEquals("value1", config.getProperty("key1")); +// assertEquals(ConfigSourceType.LOCAL, repo.getSourceType()); +// } +// +// @Test +// public void testSyncFromUpstreamWithRemote() throws Exception { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// +// Properties upstreamProperties = new Properties(); +// upstreamProperties.setProperty("key2", "value2"); +// +// when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); +// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.REMOTE); +// +// repo.sync(); +// +// Properties config = repo.getConfig(); +// assertEquals("value2", config.getProperty("key2")); +// assertEquals(ConfigSourceType.REMOTE, repo.getSourceType()); +// } +// +// @Test +// public void testSyncFromK8s() throws Exception { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// +// Properties k8sProperties = new Properties(); +// k8sProperties.setProperty("key3", "value3"); +// +// when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())) +// .thenReturn(Base64.getEncoder().encodeToString("{\"key3\":\"value3\"}".getBytes())); +// +// repo.sync(); +// +// Properties config = repo.getConfig(); +// assertEquals("value3", config.getProperty("key3")); +// assertEquals(ConfigSourceType.CONFIGMAP, repo.getSourceType()); +// } +// +// +// /** +// * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 +// */ +// @Test +// public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { +// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); +// // arrange +// ConfigRepository upstream = mock(ConfigRepository.class); +// when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); +// repo.setUpstreamRepository(upstream); +// when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); +// +// // act +// repo.sync(); +// +// // assert +// verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); +// } +// +// +// public static class MockConfigUtil extends ConfigUtil { +// @Override +// public String getAppId() { +// return someAppId; +// } +// +// @Override +// public String getCluster() { +// return someCluster; +// } +// } +// +//} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index b9f88a53..28a8b82c 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -47,6 +47,7 @@ public void tearDown() throws Exception { System.clearProperty(ApolloClientSystemConsts.APOLLO_CACHE_DIR); System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); System.clearProperty(ApolloClientSystemConsts.APOLLO_PROPERTY_NAMES_CACHE_ENABLE); + System.clearProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE); } @Test @@ -247,7 +248,7 @@ public void testDefaultLocalCacheDir() throws Exception { public void testConfigMapNamespaceWithSystemProperty() { String someConfigMapNamespace = "someConfigMapNamespace"; - System.setProperty(ApolloClientSystemConsts.APOLLO_CONFIGMAP_NAMESPACE, someConfigMapNamespace); + System.setProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE, someConfigMapNamespace); ConfigUtil configUtil = new ConfigUtil(); diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java index 0e47a664..b090fc1a 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ApolloClientSystemConsts.java @@ -76,12 +76,12 @@ public class ApolloClientSystemConsts { /** * kubernetes configmap cache namespace */ - public static final String APOLLO_CONFIGMAP_NAMESPACE = "apollo.configmap-namespace"; + public static final String APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE = "apollo.cache.kubernetes.configmap-namespace"; /** * kubernetes configmap cache namespace environment variables */ - public static final String APOLLO_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES = "APOLLO_CONFIGMAP_NAMESPACE"; + public static final String APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE_ENVIRONMENT_VARIABLES = "APOLLO_CACHE_KUBERNETES_CONFIGMAP_NAMESPACE"; /** * apollo client access key diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java index d6370ec0..30e71728 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java @@ -19,7 +19,7 @@ public interface ConfigConsts { String NAMESPACE_APPLICATION = "application"; String CLUSTER_NAME_DEFAULT = "default"; - String CLUSTER_NAMESPACE_SEPARATOR = "+"; + String CLUSTER_NAMESPACE_SEPARATOR = "-"; String APOLLO_CLUSTER_KEY = "apollo.cluster"; String APOLLO_META_KEY = "apollo.meta"; String CONFIG_FILE_CONTENT_KEY = "content"; diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java index 083d4d2d..71a682e5 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java @@ -35,7 +35,7 @@ * @author Jason Song(song_s@ctrip.com) */ public enum Env{ - LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN, KUBERNETES; + LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN; public static Env fromString(String env) { Env environment = EnvUtils.transformEnv(env); diff --git a/changes/changes-2.4.0.md b/changes/changes-2.4.0.md deleted file mode 100644 index a099c1ab..00000000 --- a/changes/changes-2.4.0.md +++ /dev/null @@ -1,12 +0,0 @@ -Changes by Version -================== -Release Notes. - -Apollo Java 2.4.0 - ------------------- -* [Feature Support Kubernetes ConfigMap cache for Apollo java, golang client](https://github.com/apolloconfig/apollo-java/pull/79) - - ------------------- -All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/4?closed=1) \ No newline at end of file diff --git a/pom.xml b/pom.xml index d153bf11..30728547 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ UTF-8 2.7.18 3.1.0 + 18.0.0 3.10.1 2.22.2 From 3a22a386c1978a94ea77cd2657e0ee8e35adc0e1 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sat, 19 Oct 2024 19:43:13 +0800 Subject: [PATCH 10/25] optional pom --- apollo-client/pom.xml | 9 ++++----- pom.xml | 9 +++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml index a8c70ece..b37ce1fc 100644 --- a/apollo-client/pom.xml +++ b/apollo-client/pom.xml @@ -70,6 +70,10 @@ spring-boot-configuration-processor true + + io.kubernetes + client-java + org.eclipse.jetty @@ -98,10 +102,5 @@ test - - io.kubernetes - client-java - ${client-java.version} - diff --git a/pom.xml b/pom.xml index 30728547..bbc449f6 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,11 @@ - 2.4.0-SNAPSHOT + 2.4.0-SNAPSHOT2 1.8 UTF-8 2.7.18 3.1.0 - 18.0.0 3.10.1 2.22.2 @@ -158,6 +157,12 @@ pom import + + io.kubernetes + client-java + 18.0.0 + true + From 65ce65aa5d95310b0044ea139417a3bdd10db4e2 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sat, 19 Oct 2024 20:02:48 +0800 Subject: [PATCH 11/25] =?UTF-8?q?fix=EF=BC=9Apom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1980cf73..a255aabd 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ - 2.4.0-SNAPSHOT2 + 2.4.0-SNAPSHOT 1.8 UTF-8 2.7.18 From 88b1d756faa504e11749206bfd30e45919d8b24f Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sat, 19 Oct 2024 23:18:31 +0800 Subject: [PATCH 12/25] =?UTF-8?q?fix=EF=BC=9Aut=20for=20k8sManager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apollo/Kubernetes/KubernetesManager.java | 29 -- .../K8sConfigMapConfigRepository.java | 17 +- .../framework/apollo/util/ConfigUtil.java | 6 +- .../Kubernetes/KubernetesManagerTest.java | 202 +++++--------- .../K8sConfigMapConfigRepositoryTest.java | 251 +++++++++--------- 5 files changed, 205 insertions(+), 300 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java index 909098c2..ed8d82ad 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java @@ -88,35 +88,6 @@ public String createConfigMap(String configMapNamespace, String name, Map data = configMap.getData(); - if (data != null && data.containsKey(name)) { - return data.get(name); - } else { - throw new RuntimeException(String.format("Specified key not found in ConfigMap: %s, configMapNamespace: %s, name: %s", name, configMapNamespace, name)); - } - } catch (Exception e) { - throw new RuntimeException(String - .format("get config map failed, configMapNamespace: %s, name: %s", configMapNamespace, name)); - } - } - /** * get value from config map * diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 6e704401..278abaf5 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -32,9 +32,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.slf4j.Logger; -import org.springframework.stereotype.Service; -import java.io.IOException; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; @@ -200,27 +198,24 @@ protected void sync() { } } - public Properties loadFromK8sConfigMap() throws IOException { + public Properties loadFromK8sConfigMap() { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); - Properties properties = null; try { String jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configUtil.getAppId(), configMapKey); if (jsonConfig == null) { // TODO 重试访问idc,default - jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(configUtil, namespace)); + String fallbackKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(configUtil, namespace); + jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, fallbackKey); } // Convert jsonConfig to properties - properties = propertiesFactory.getPropertiesInstance(); + Properties properties = propertiesFactory.getPropertiesInstance(); if (jsonConfig != null && !jsonConfig.isEmpty()) { Gson gson = new Gson(); - Type type = new TypeToken>() { - }.getType(); + Type type = new TypeToken>() {}.getType(); Map configMap = gson.fromJson(jsonConfig, type); - for (Map.Entry entry : configMap.entrySet()) { - properties.setProperty(entry.getKey(), entry.getValue()); - } + configMap.forEach(properties::setProperty); } return properties; } catch (Exception ex) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index 27f84d0b..a1d06eb9 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -534,10 +534,6 @@ private void initOverrideSystemProperties() { ApolloClientSystemConsts.APOLLO_OVERRIDE_SYSTEM_PROPERTIES, overrideSystemProperties); } - - - private void initClientMonitorExternalType() { - monitorExternalType = System.getProperty(ApolloClientSystemConsts.APOLLO_CLIENT_MONITOR_EXTERNAL_TYPE); private void initPropertyKubernetesCacheEnabled() { PropertyKubernetesCacheEnabled = getPropertyBoolean(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE, @@ -545,6 +541,8 @@ private void initPropertyKubernetesCacheEnabled() { PropertyKubernetesCacheEnabled); } + private void initClientMonitorExternalType() { + monitorExternalType = System.getProperty(ApolloClientSystemConsts.APOLLO_CLIENT_MONITOR_EXTERNAL_TYPE); if (Strings.isNullOrEmpty(monitorExternalType)) { monitorExternalType = Foundation.app() .getProperty(ApolloClientSystemConsts.APOLLO_CLIENT_MONITOR_EXTERNAL_TYPE, "NONE"); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java index b3b22e42..f806349d 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java @@ -16,18 +16,13 @@ */ package com.ctrip.framework.apollo.Kubernetes; -import io.kubernetes.client.openapi.ApiClient; +import com.ctrip.framework.apollo.build.MockInjector; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ObjectMeta; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; import java.util.HashMap; import java.util.Map; @@ -35,7 +30,6 @@ import static org.mockito.Mockito.*; import static org.junit.Assert.*; -@RunWith(MockitoJUnitRunner.class) public class KubernetesManagerTest { private CoreV1Api coreV1Api; @@ -43,44 +37,28 @@ public class KubernetesManagerTest { @Before public void setUp() { - coreV1Api = Mockito.mock(CoreV1Api.class); + coreV1Api = mock(CoreV1Api.class); kubernetesManager = new KubernetesManager(coreV1Api); - } + MockInjector.setInstance(KubernetesManager.class, kubernetesManager); + MockInjector.setInstance(CoreV1Api.class, coreV1Api); + } /** * 测试 createConfigMap 成功创建配置 */ -// @Test -// public void testCreateConfigMapSuccess() throws Exception { -// // arrange -// String namespace = "default"; -// String name = "testConfigMap"; -// Map data = new HashMap<>(); -// data.put("key", "value"); -// V1ConfigMap configMap = new V1ConfigMap() -// .metadata(new V1ObjectMeta().name(name).namespace(namespace)) -// .data(data); -// -// when(coreV1Api.createNamespacedConfigMap(eq(namespace), eq(configMap), isNull(), isNull(), isNull(),isNull())).thenReturn(configMap); -// -// // act -// String result = kubernetesManager.createConfigMap(namespace, name, data); -// -// // assert -// verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); -// assert name.equals(result); -// } - - /** - * 测试 createConfigMap 传入 null 作为数据,正常执行 - */ @Test - public void testCreateConfigMapNullData() throws Exception { + public void testCreateConfigMapSuccess() throws Exception { // arrange String namespace = "default"; String name = "testConfigMap"; - Map data = null; + Map data = new HashMap<>(); + data.put("key", "value"); + V1ConfigMap configMap = new V1ConfigMap() + .metadata(new V1ObjectMeta().name(name).namespace(namespace)) + .data(data); + + when(coreV1Api.createNamespacedConfigMap(eq(namespace), eq(configMap), isNull(), isNull(), isNull(),isNull())).thenReturn(configMap); // act String result = kubernetesManager.createConfigMap(namespace, name, data); @@ -91,80 +69,45 @@ public void testCreateConfigMapNullData() throws Exception { } /** - * 测试loadFromConfigMap方法在正常情况下的行为 - */ -// @Test -// public void testLoadFromConfigMapSuccess() throws Exception { -// // arrange -// String namespace = "TestNamespace"; -// String name = "TestName"; -// V1ConfigMap configMap = new V1ConfigMap(); -// configMap.putDataItem("testKey", "TestValue"); -// when(coreV1Api.readNamespacedConfigMap(anyString(), anyString(), isNull())).thenReturn(configMap); -// -// // act -// String result = kubernetesManager.loadFromConfigMap(namespace, name); -// -// // assert -// assertEquals("TestValue", result); -// } - - /** - * 测试loadFromConfigMap方法在抛出异常时的行为 + * 测试 createConfigMap 传入 null 作为数据,正常执行 */ @Test - public void testLoadFromConfigMapFailure() throws Exception { + public void testCreateConfigMapNullData() throws Exception { // arrange - String namespace = "TestNamespace"; - String name = "TestName"; - when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenThrow(new ApiException("Kubernetes Manager Exception")); + String namespace = "default"; + String name = "testConfigMap"; + Map data = null; - assertThrows(String.format("get config map failed, configMapNamespace: %s, name: %s", namespace, name), RuntimeException.class, () -> { - kubernetesManager.loadFromConfigMap(namespace, name); - }); + // act + String result = kubernetesManager.createConfigMap(namespace, name, data); + // assert + verify(coreV1Api, times(1)).createNamespacedConfigMap(eq(namespace), any(V1ConfigMap.class), isNull(), isNull(), isNull(),isNull()); + assert name.equals(result); } /** - * 测试loadFromConfigMap方法在ConfigMap不存在时的行为 + * 测试getValueFromConfigMap方法,当ConfigMap存在且包含指定key时返回正确的value */ @Test - public void testLoadFromConfigMapConfigMapNotFound() throws Exception { + public void testGetValueFromConfigMapReturnsValue() throws Exception { // arrange - String namespace = "TestNamespace"; - String name = "TestName"; - when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(null); + String namespace = "default"; + String name = "testConfigMap"; + String key = "testKey"; + String expectedValue = "testValue"; + V1ConfigMap configMap = new V1ConfigMap(); + configMap.putDataItem(key, expectedValue); + + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); // act - assertThrows(String.format("get config map failed, configMapNamespace: %s, name: %s", namespace, name), RuntimeException.class, () -> { - kubernetesManager.loadFromConfigMap(namespace, name); - }); + String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); + // assert + assertEquals(expectedValue, actualValue); } - /** - * 测试getValueFromConfigMap方法,当ConfigMap存在且包含指定key时返回正确的value - */ -// @Test -// public void testGetValueFromConfigMapReturnsValue() throws Exception { -// // arrange -// String namespace = "default"; -// String name = "testConfigMap"; -// String key = "testKey"; -// String expectedValue = "testValue"; -// V1ConfigMap configMap = new V1ConfigMap(); -// configMap.putDataItem(key, expectedValue); -// -// System.out.println(configMap.toString()); -// when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); -// -// // act -// String actualValue = kubernetesManager.getValueFromConfigMap(namespace, name, key); -// -// // assert -// assertEquals(expectedValue, actualValue); -// } - /** * 测试getValueFromConfigMap方法,当ConfigMap不存在指定key时返回null */ @@ -187,46 +130,47 @@ public void testGetValueFromConfigMapKeyNotFound() throws Exception { /** * 测试updateConfigMap成功的情况 */ -// @Test -// public void testUpdateConfigMapSuccess() throws Exception { -// // arrange -// String configMapNamespace = "default"; -// String name = "testConfigMap"; -// Map data = new HashMap<>(); -// data.put("key", "value"); -// V1ConfigMap configMap = new V1ConfigMap(); -// configMap.metadata(new V1ObjectMeta().name(name).namespace(configMapNamespace)); -// configMap.data(data); -// -// when(coreV1Api.replaceNamespacedConfigMap(name, configMapNamespace, configMap, null, null, null, "fieldManagerValue")).thenReturn(configMap); -// -// // act -// Boolean success = kubernetesManager.updateConfigMap(configMapNamespace, name, data); -// -// // assert -// assertTrue(success); -// } + @Test + public void testUpdateConfigMapSuccess() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + Map data = new HashMap<>(); + data.put("key", "value"); + V1ConfigMap configMap = new V1ConfigMap(); + configMap.metadata(new V1ObjectMeta().name(name).namespace(namespace)); + configMap.data(data); + + when(coreV1Api.readNamespacedConfigMap(name, namespace, null)).thenReturn(configMap); + when(coreV1Api.replaceNamespacedConfigMap(name, namespace, configMap, null, null, null, null)).thenReturn(configMap); + + // act + Boolean success = kubernetesManager.updateConfigMap(namespace, name, data); + + // assert + assertTrue(success); + } /** * 测试ConfigMap存在时,checkConfigMapExist方法返回true */ -// @Test -// public void testCheckConfigMapExistWhenConfigMapExists() throws Exception { -// // arrange -// String namespace = "default"; -// String name = "testConfigMap"; -// -// // 创建一个模拟的 V1ConfigMap 实例 -// V1ConfigMap mockConfigMap = new V1ConfigMap(); -// mockConfigMap.setMetadata(new V1ObjectMeta().name(name).namespace(namespace)); -// doReturn(mockConfigMap).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); -// -// // act -// boolean result = kubernetesManager.checkConfigMapExist(namespace, name); -// -// // assert -// assertEquals(true, result); -// } + @Test + public void testCheckConfigMapExistWhenConfigMapExists() throws Exception { + // arrange + String namespace = "default"; + String name = "testConfigMap"; + + // 创建一个模拟的 V1ConfigMap 实例 + V1ConfigMap mockConfigMap = new V1ConfigMap(); + mockConfigMap.setMetadata(new V1ObjectMeta().name(name).namespace(namespace)); + doReturn(mockConfigMap).when(coreV1Api).readNamespacedConfigMap(name, namespace, null); + + // act + boolean result = kubernetesManager.checkConfigMapExist(namespace, name); + + // assert + assertEquals(true, result); + } /** * 测试ConfigMap不存在的情况,返回false diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 7631ea29..0c8b3828 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -1,112 +1,122 @@ -///* -// * Copyright 2022 Apollo Authors -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// * -// */ -//package com.ctrip.framework.apollo.internals; -// -//import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; -//import com.ctrip.framework.apollo.build.MockInjector; -//import com.ctrip.framework.apollo.enums.ConfigSourceType; -//import com.ctrip.framework.apollo.exceptions.ApolloConfigException; -//import com.ctrip.framework.apollo.util.ConfigUtil; -//import com.ctrip.framework.apollo.util.factory.PropertiesFactory; -//import io.kubernetes.client.openapi.ApiClient; -//import io.kubernetes.client.openapi.apis.CoreV1Api; -//import org.junit.After; -//import org.junit.Before; -//import org.junit.Test; -//import org.mockito.ArgumentCaptor; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.MockitoAnnotations; -//import org.mockito.invocation.InvocationOnMock; -//import org.mockito.stubbing.Answer; -// -//import java.util.Base64; -//import java.util.Properties; -// -//import static org.junit.Assert.assertEquals; -//import static org.mockito.Mockito.*; -//import static org.springframework.test.util.ReflectionTestUtils.setField; -// -//public class K8sConfigMapConfigRepositoryTest { -// private String someNamespace; -// private ConfigRepository upstreamRepo; -// private Properties someProperties; -// private static String someAppId = "someApp"; -// private static String someCluster = "someCluster"; -// private String defaultKey; -// private String defaultValue; -// private ConfigSourceType someSourceType; -// -// @Mock -// private CoreV1Api coreV1Api; -// @Mock -// private ApiClient client; -// -// @InjectMocks -// private KubernetesManager kubernetesManager; -// @Mock -// private ConfigUtil configUtil; -// -// @Before -// public void setUp() { -// someNamespace = "someName"; -// -// // 初始化上游数据源 -// someProperties = new Properties(); -// defaultKey = "defaultKey"; -// defaultValue = "defaultValue"; -// someProperties.setProperty(defaultKey, defaultValue); -// someSourceType = ConfigSourceType.LOCAL; -// upstreamRepo = mock(ConfigRepository.class); -// when(upstreamRepo.getConfig()).thenReturn(someProperties); -// when(upstreamRepo.getSourceType()).thenReturn(someSourceType); -// -// // mock configutil类 -// MockitoAnnotations.initMocks(this); -// when(configUtil.getAppId()).thenReturn("testAppId"); -// when(configUtil.getCluster()).thenReturn("default"); -// when(configUtil.getConfigMapNamespace()).thenReturn("default"); -// -// MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); -// PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); -// when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { -// @Override -// public Properties answer(InvocationOnMock invocation) { -// return new Properties(); -// } -// }); -// MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); -// } -// -// @After -// public void tearDown() throws Exception { -// MockInjector.reset(); -// } -// -// @Test(expected = ApolloConfigException.class) -// public void testConstructorWithNullNamespace() { -// new K8sConfigMapConfigRepository(null); -// } -// +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.internals; + +import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.build.MockInjector; +import com.ctrip.framework.apollo.enums.ConfigSourceType; +import com.ctrip.framework.apollo.exceptions.ApolloConfigException; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.ctrip.framework.apollo.util.factory.PropertiesFactory; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class K8sConfigMapConfigRepositoryTest { + private String someNamespace = "default"; + private ConfigRepository upstreamRepo; + private Properties someProperties; + private static final String someAppId = "someApp"; + private static final String someCluster = "someCluster"; + private static final String defaultKey = "defaultKey"; + private static final String defaultValue = "defaultValue"; + private ConfigSourceType someSourceType; + private V1ConfigMap configMap; + + @Mock + private CoreV1Api coreV1Api; + + @InjectMocks + private KubernetesManager kubernetesManager; + @Mock + private ConfigUtil configUtil; + private K8sConfigMapConfigRepository configmapRepo; + + @Before + public void setUp() { + someProperties = new Properties(); + someProperties.setProperty(defaultKey, defaultValue); + + Map data = new HashMap<>(); + data.put(defaultKey, defaultValue); + configMap = new V1ConfigMap() + .metadata(new V1ObjectMeta().name(someAppId).namespace(someNamespace)) + .data(data); + + someSourceType = ConfigSourceType.LOCAL; + upstreamRepo = mock(ConfigRepository.class); + when(upstreamRepo.getConfig()).thenReturn(someProperties); + when(upstreamRepo.getSourceType()).thenReturn(someSourceType); + + // mock ConfigUtil + configUtil = mock(ConfigUtil.class); + when(configUtil.getAppId()).thenReturn("testAppId"); + when(configUtil.getCluster()).thenReturn("default"); + when(configUtil.getConfigMapNamespace()).thenReturn("default"); + when(configUtil.isPropertyKubernetesCacheEnabled()).thenReturn(true); + when(configUtil.isOverrideSystemProperties()).thenReturn(false); + when(configUtil.getDefaultLocalCacheDir()).thenReturn("/cache"); + when(configUtil.getMetaServerDomainName()).thenReturn("http://meta.server"); + + MockInjector.setInstance(ConfigUtil.class, configUtil); + MockInjector.setInstance(CoreV1Api.class, coreV1Api); + + kubernetesManager = new KubernetesManager(coreV1Api); + MockInjector.setInstance(KubernetesManager.class, kubernetesManager); + + configmapRepo = mock(K8sConfigMapConfigRepository.class); + MockInjector.setInstance(K8sConfigMapConfigRepository.class, configmapRepo); + + PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); + when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { + @Override + public Properties answer(InvocationOnMock invocation) { + return new Properties(); + } + }); + MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + } + + @After + public void tearDown() throws Exception { + MockInjector.reset(); + } + // @Test // public void testSetConfigMapKey() { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// repo.setConfigMapKey(someCluster, someNamespace); -// assertEquals(someCluster + someNamespace, repo.getConfigMapKey()); +// when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); +// configmapRepo.setConfigMapKey(someCluster, someNamespace); +// assertEquals(someCluster +"-"+ someNamespace, configmapRepo.getConfigMapKey()); // } // // @Test @@ -116,12 +126,12 @@ // assertEquals(someAppId, repo.getConfigMapName()); // } // -// @Test(expected = ApolloConfigException.class) -// public void testSetConfigMapNameWithNullAppId() { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// repo.setConfigMapName(null, false); -// } -// + @Test(expected = ApolloConfigException.class) + public void testSetConfigMapNameWithNullAppId() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + repo.setConfigMapName(null, false); + } + // // @Test // public void testOnRepositoryChange() throws Exception { @@ -272,17 +282,4 @@ // verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); // } // -// -// public static class MockConfigUtil extends ConfigUtil { -// @Override -// public String getAppId() { -// return someAppId; -// } -// -// @Override -// public String getCluster() { -// return someCluster; -// } -// } -// -//} +} From c3fc604f9638de035a20fa5c31cb53e29ef23305 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Mon, 21 Oct 2024 10:19:44 +0800 Subject: [PATCH 13/25] =?UTF-8?q?fix=EF=BC=9Arename=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../K8sConfigMapConfigRepository.java | 16 ++++++- .../KubernetesManager.java | 2 +- .../K8sConfigMapConfigRepositoryTest.java | 47 ++++++++----------- .../KubernetesManagerTest.java | 2 +- 4 files changed, 36 insertions(+), 31 deletions(-) rename apollo-client/src/main/java/com/ctrip/framework/apollo/{Kubernetes => kubernetes}/KubernetesManager.java (99%) rename apollo-client/src/test/java/com/ctrip/framework/apollo/{Kubernetes => kubernetes}/KubernetesManagerTest.java (99%) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 278abaf5..340fd548 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -16,7 +16,7 @@ */ package com.ctrip.framework.apollo.internals; -import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.kubernetes.KubernetesManager; import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.utils.DeferredLoggerFactory; @@ -60,7 +60,19 @@ public class K8sConfigMapConfigRepository extends AbstractConfigRepository * @param namespace the namespace */ public K8sConfigMapConfigRepository(String namespace) { - this(namespace, null); + this(namespace, (ConfigRepository) null); + } + + public K8sConfigMapConfigRepository(String namespace, KubernetesManager kubernetesManager) { + this.namespace = namespace; + configUtil = ApolloInjector.getInstance(ConfigUtil.class); + kubernetesManager = ApolloInjector.getInstance(KubernetesManager.class); + configMapNamespace = configUtil.getConfigMapNamespace(); + this.kubernetesManager = kubernetesManager; + + this.setConfigMapKey(configUtil.getCluster(), namespace); + this.setConfigMapName(configUtil.getAppId(), false); + this.setUpstreamRepository(upstream); } public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java similarity index 99% rename from apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java rename to apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index ed8d82ad..dacab57a 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package com.ctrip.framework.apollo.Kubernetes; +package com.ctrip.framework.apollo.kubernetes; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 0c8b3828..344d4c9f 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -16,7 +16,7 @@ */ package com.ctrip.framework.apollo.internals; -import com.ctrip.framework.apollo.Kubernetes.KubernetesManager; +import com.ctrip.framework.apollo.kubernetes.KubernetesManager; import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.ConfigSourceType; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; @@ -28,13 +28,12 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; +import org.junit.jupiter.api.BeforeEach; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -52,8 +51,6 @@ public class K8sConfigMapConfigRepositoryTest { private static final String defaultValue = "defaultValue"; private ConfigSourceType someSourceType; private V1ConfigMap configMap; - - @Mock private CoreV1Api coreV1Api; @InjectMocks @@ -88,22 +85,18 @@ public void setUp() { when(configUtil.getDefaultLocalCacheDir()).thenReturn("/cache"); when(configUtil.getMetaServerDomainName()).thenReturn("http://meta.server"); + coreV1Api = mock(CoreV1Api.class); MockInjector.setInstance(ConfigUtil.class, configUtil); MockInjector.setInstance(CoreV1Api.class, coreV1Api); kubernetesManager = new KubernetesManager(coreV1Api); MockInjector.setInstance(KubernetesManager.class, kubernetesManager); - configmapRepo = mock(K8sConfigMapConfigRepository.class); + configmapRepo = new K8sConfigMapConfigRepository(someNamespace, kubernetesManager); MockInjector.setInstance(K8sConfigMapConfigRepository.class, configmapRepo); PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); - when(propertiesFactory.getPropertiesInstance()).thenAnswer(new Answer() { - @Override - public Properties answer(InvocationOnMock invocation) { - return new Properties(); - } - }); + when(propertiesFactory.getPropertiesInstance()).thenReturn(new Properties()); MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); } @@ -125,14 +118,14 @@ public void tearDown() throws Exception { // repo.setConfigMapName(someAppId, false); // assertEquals(someAppId, repo.getConfigMapName()); // } -// - @Test(expected = ApolloConfigException.class) + + @Test(expected = IllegalArgumentException.class) public void testSetConfigMapNameWithNullAppId() { K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); repo.setConfigMapName(null, false); } -// + // @Test // public void testOnRepositoryChange() throws Exception { // RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); @@ -171,18 +164,18 @@ public void testSetConfigMapNameWithNullAppId() { // // 断言 K8sConfigMapConfigRepository 的源类型更新为 anotherSourceType // assertEquals(anotherSourceType, k8sConfigMapConfigRepository.getSourceType()); // } -// -// /** -// * 测试persistConfigMap方法成功持久化配置信息 -// */ -// @Test -// public void testPersistConfigMap() { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); -// repo.persistConfigMap(someProperties); -// verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); -// } -// + + /** + * 测试persistConfigMap方法成功持久化配置信息 + */ + @Test + public void testPersistConfigMap() { + K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); + doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); + repo.persistConfigMap(someProperties); + verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); + } + // /** // * 测试sync方法成功从上游数据源同步 // */ diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/kubernetes/KubernetesManagerTest.java similarity index 99% rename from apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java rename to apollo-client/src/test/java/com/ctrip/framework/apollo/kubernetes/KubernetesManagerTest.java index f806349d..515f96d3 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/Kubernetes/KubernetesManagerTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/kubernetes/KubernetesManagerTest.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package com.ctrip.framework.apollo.Kubernetes; +package com.ctrip.framework.apollo.kubernetes; import com.ctrip.framework.apollo.build.MockInjector; import io.kubernetes.client.openapi.ApiException; From 79ae56a0b9f1b2203b8d9c8cef539d5926c7d27a Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 23 Oct 2024 20:38:25 +0800 Subject: [PATCH 14/25] =?UTF-8?q?fix=EF=BC=9Arename=20k8sNamespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../K8sConfigMapConfigRepository.java | 37 +++++------ .../apollo/kubernetes/KubernetesManager.java | 65 ++++++++++--------- .../framework/apollo/util/ConfigUtil.java | 32 ++++----- .../K8sConfigMapConfigRepositoryTest.java | 16 ++--- .../LocalFileConfigRepositoryTest.java | 1 - .../kubernetes/KubernetesManagerTest.java | 2 +- .../framework/apollo/util/ConfigUtilTest.java | 14 ++-- .../apollo/core/ApolloClientSystemConsts.java | 4 +- .../framework/apollo/core/ConfigConsts.java | 1 + 9 files changed, 85 insertions(+), 87 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 340fd548..07dd0226 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -47,7 +47,7 @@ public class K8sConfigMapConfigRepository extends AbstractConfigRepository private final String namespace; private String configMapName; private String configMapKey; - private final String configMapNamespace; + private final String k8sNamespace; private final ConfigUtil configUtil; private final KubernetesManager kubernetesManager; private volatile Properties configMapProperties; @@ -66,8 +66,7 @@ public K8sConfigMapConfigRepository(String namespace) { public K8sConfigMapConfigRepository(String namespace, KubernetesManager kubernetesManager) { this.namespace = namespace; configUtil = ApolloInjector.getInstance(ConfigUtil.class); - kubernetesManager = ApolloInjector.getInstance(KubernetesManager.class); - configMapNamespace = configUtil.getConfigMapNamespace(); + k8sNamespace = configUtil.getK8sNamespace(); this.kubernetesManager = kubernetesManager; this.setConfigMapKey(configUtil.getCluster(), namespace); @@ -79,7 +78,7 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.namespace = namespace; configUtil = ApolloInjector.getInstance(ConfigUtil.class); kubernetesManager = ApolloInjector.getInstance(KubernetesManager.class); - configMapNamespace = configUtil.getConfigMapNamespace(); + k8sNamespace = configUtil.getK8sNamespace(); this.setConfigMapKey(configUtil.getCluster(), namespace); this.setConfigMapName(configUtil.getAppId(), false); @@ -87,8 +86,7 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) } void setConfigMapKey(String cluster, String namespace) { - // TODO 兜底key怎么设计不会冲突(cluster初始化时已经设置了层级) - // cluster: 用户定义>idc>default,所以已经不需要额外层级设置了 + // cluster: 用户定义>idc>default if (StringUtils.isBlank(cluster)) { configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); return; @@ -105,6 +103,7 @@ public String getConfigMapName() { } void setConfigMapName(String appId, boolean syncImmediately) { + //configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; configMapName = appId; // 初始化configmap this.checkConfigMapName(configMapName); @@ -117,14 +116,14 @@ private void checkConfigMapName(String configMapName) { if (StringUtils.isBlank(configMapName)) { throw new IllegalArgumentException("ConfigMap name cannot be null"); } - if (kubernetesManager.checkConfigMapExist(configMapNamespace, configMapName)) { + if (kubernetesManager.checkConfigMapExist(k8sNamespace, configMapName)) { return; } // Create an empty configmap, write the new value in the update event Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "createK8sConfigMap"); transaction.addData("configMapName", configMapName); try { - kubernetesManager.createConfigMap(configMapNamespace, configMapName, null); + kubernetesManager.createConfigMap(k8sNamespace, configMapName, null); transaction.setStatus(Transaction.SUCCESS); } catch (Throwable ex) { Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); @@ -135,12 +134,6 @@ private void checkConfigMapName(String configMapName) { } } - /** - * TODO 测试点: - * 1. 从上游成功恢复(开启文件存储) - * 2. 从上游成功恢复(没开启文件存储,从remote) - * 3. 从k8s成功恢复 - */ @Override public Properties getConfig() { if (configMapProperties == null) { @@ -148,7 +141,7 @@ public Properties getConfig() { } Properties result = propertiesFactory.getPropertiesInstance(); result.putAll(configMapProperties); - logger.info("configmap值:{}", configMapProperties); + logger.info("configmap properties: {}", configMapProperties); return result; } @@ -214,11 +207,11 @@ public Properties loadFromK8sConfigMap() { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); try { - String jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configUtil.getAppId(), configMapKey); + String jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); if (jsonConfig == null) { - // TODO 重试访问idc,default - String fallbackKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(configUtil, namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(configMapNamespace, configMapName, fallbackKey); + // TODO 这样重试访问idc,default是否正确 + String retryKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); + jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, retryKey); } // Convert jsonConfig to properties @@ -237,7 +230,7 @@ public Properties loadFromK8sConfigMap() { } } - private boolean trySyncFromUpstream() { + public boolean trySyncFromUpstream() { if (upstream == null) { return false; } @@ -282,7 +275,7 @@ public void onRepositoryChange(String namespace, Properties newProperties) { public void persistConfigMap(Properties properties) { Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); transaction.addData("configMapName", configUtil.getAppId()); - transaction.addData("configMapNamespace", configUtil.getConfigMapNamespace()); + transaction.addData("k8sNamespace", configUtil.getK8sNamespace()); try { // Convert properties to a JSON string using Gson Gson gson = new Gson(); @@ -291,7 +284,7 @@ public void persistConfigMap(Properties properties) { data.put(configMapKey, jsonConfig); // update configmap - kubernetesManager.updateConfigMap(configUtil.getConfigMapNamespace(), configUtil.getAppId(), data); + kubernetesManager.updateConfigMap(configUtil.getK8sNamespace(), configUtil.getAppId(), data); transaction.setStatus(Transaction.SUCCESS); } catch (Exception ex) { ApolloConfigException exception = diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index dacab57a..e37a7190 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -16,6 +16,7 @@ */ package com.ctrip.framework.apollo.kubernetes; +import com.ctrip.framework.apollo.core.utils.StringUtils; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; @@ -35,7 +36,7 @@ public class KubernetesManager { private ApiClient client; private CoreV1Api coreV1Api; - private final Logger log = LoggerFactory.getLogger(this.getClass()); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); public KubernetesManager() { try { @@ -44,7 +45,7 @@ public KubernetesManager() { coreV1Api = new CoreV1Api(client); } catch (Exception e) { String errorMessage = "Failed to initialize Kubernetes client: " + e.getMessage(); - log.error(errorMessage, e); + logger.error(errorMessage, e); throw new RuntimeException(errorMessage, e); } } @@ -68,22 +69,24 @@ public V1ConfigMap buildConfigMap(String name, String namespace, Map data) { - if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty()) { - log.error("create configmap failed due to null or empty parameter: configMapNamespace={}, name={}", configMapNamespace, name); + public String createConfigMap(String k8sNamespace, String name, Map data) { + if (StringUtils.isEmpty(k8sNamespace) || StringUtils.isEmpty(name)) { + logger.error("create configmap failed due to null or empty parameter: k8sNamespace={}, name={}", k8sNamespace, name); + return null; } - V1ConfigMap configMap = buildConfigMap(name, configMapNamespace, data); + V1ConfigMap configMap = buildConfigMap(name, k8sNamespace, data); try { - coreV1Api.createNamespacedConfigMap(configMapNamespace, configMap, null, null, null, null); - log.info("ConfigMap created successfully: name: {}, namespace: {}", name, configMapNamespace); + coreV1Api.createNamespacedConfigMap(k8sNamespace, configMap, null, null, null, null); + logger.info("ConfigMap created successfully: name: {}, namespace: {}", name, k8sNamespace); return name; } catch (Exception e) { + logger.error("Failed to create ConfigMap: {}", e.getMessage(), e); throw new RuntimeException("Failed to create ConfigMap: " + e.getMessage(), e); } } @@ -91,23 +94,24 @@ public String createConfigMap(String configMapNamespace, String name, Map data) { - if (configMapNamespace == null || configMapNamespace.isEmpty() || name == null || name.isEmpty() || data == null || data.isEmpty()) { - log.error("Parameters can not be null or empty: configMapNamespace={}, name={}", configMapNamespace, name); + public boolean updateConfigMap(String k8sNamespace, String name, Map data) { + if (StringUtils.isEmpty(k8sNamespace) || StringUtils.isEmpty(name)) { + logger.error("Parameters can not be null or empty: k8sNamespace={}, name={}", k8sNamespace, name); return false; } @@ -134,14 +138,14 @@ public boolean updateConfigMap(String configMapNamespace, String name, Map Date: Wed, 23 Oct 2024 20:46:43 +0800 Subject: [PATCH 15/25] =?UTF-8?q?fix=EF=BC=9Arename=20configmapName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internals/K8sConfigMapConfigRepository.java | 17 ++++------------- .../K8sConfigMapConfigRepositoryTest.java | 6 ------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 07dd0226..e7aebade 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -94,17 +94,8 @@ void setConfigMapKey(String cluster, String namespace) { configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(cluster, namespace); } - public String getConfigMapKey() { - return configMapKey; - } - - public String getConfigMapName() { - return configMapName; - } - void setConfigMapName(String appId, boolean syncImmediately) { - //configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; - configMapName = appId; + configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; // 初始化configmap this.checkConfigMapName(configMapName); if (syncImmediately) { @@ -274,8 +265,8 @@ public void onRepositoryChange(String namespace, Properties newProperties) { public void persistConfigMap(Properties properties) { Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); - transaction.addData("configMapName", configUtil.getAppId()); - transaction.addData("k8sNamespace", configUtil.getK8sNamespace()); + transaction.addData("configMapName", configMapName); + transaction.addData("k8sNamespace", k8sNamespace); try { // Convert properties to a JSON string using Gson Gson gson = new Gson(); @@ -284,7 +275,7 @@ public void persistConfigMap(Properties properties) { data.put(configMapKey, jsonConfig); // update configmap - kubernetesManager.updateConfigMap(configUtil.getK8sNamespace(), configUtil.getAppId(), data); + kubernetesManager.updateConfigMap(k8sNamespace, configMapName, data); transaction.setStatus(Transaction.SUCCESS); } catch (Exception ex) { ApolloConfigException exception = diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index c5c98f73..e5296d2a 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -112,12 +112,6 @@ public void tearDown() throws Exception { // assertEquals(someCluster +"-"+ someNamespace, configmapRepo.getConfigMapKey()); // } // -// @Test -// public void testSetConfigMapName() { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// repo.setConfigMapName(someAppId, false); -// assertEquals(someAppId, repo.getConfigMapName()); -// } @Test(expected = IllegalArgumentException.class) public void testSetConfigMapNameWithNullAppId() { From 82d61d757bf020a73cf3cfa909620c66be15219d Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 23 Oct 2024 21:02:40 +0800 Subject: [PATCH 16/25] fix --- .../internals/K8sConfigMapConfigRepository.java | 9 +++++++++ .../internals/K8sConfigMapConfigRepositoryTest.java | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index e7aebade..cbd94029 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -85,6 +85,14 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.setUpstreamRepository(upstream); } + public String getConfigMapKey() { + return configMapKey; + } + + public String getConfigMapName() { + return configMapName; + } + void setConfigMapKey(String cluster, String namespace) { // cluster: 用户定义>idc>default if (StringUtils.isBlank(cluster)) { @@ -95,6 +103,7 @@ void setConfigMapKey(String cluster, String namespace) { } void setConfigMapName(String appId, boolean syncImmediately) { + Preconditions.checkNotNull(appId, "AppId cannot be null"); configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; // 初始化configmap this.checkConfigMapName(configMapName); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index e5296d2a..2c2c06ba 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -46,6 +46,7 @@ public class K8sConfigMapConfigRepositoryTest { private ConfigRepository upstreamRepo; private Properties someProperties; private static final String someAppId = "someApp"; + private static final String someConfigmapName = "apollo-configcache-someApp"; private static final String someCluster = "someCluster"; private static final String defaultKey = "defaultKey"; private static final String defaultValue = "defaultValue"; @@ -113,10 +114,15 @@ public void tearDown() throws Exception { // } // - @Test(expected = IllegalArgumentException.class) + @Test(expected = Exception.class) public void testSetConfigMapNameWithNullAppId() { - K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); - repo.setConfigMapName(null, false); + configmapRepo.setConfigMapName(null, false); + } + + @Test + public void testSetConfigMapName() { + configmapRepo.setConfigMapName(someAppId, false); + assertEquals(someConfigmapName, configmapRepo.getConfigMapName()); } From 2f5ddf6d15aa352304d1ef458714513d29a2751d Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sun, 27 Oct 2024 20:27:07 +0800 Subject: [PATCH 17/25] fix --- CHANGES.md | 3 +- apollo-client/pom.xml | 1 + .../K8sConfigMapConfigRepository.java | 42 ++++--------------- .../apollo/kubernetes/KubernetesManager.java | 2 - .../apollo/spi/DefaultConfigFactory.java | 13 +++++- .../K8sConfigMapConfigRepositoryTest.java | 2 +- .../framework/apollo/core/ConfigConsts.java | 3 +- 7 files changed, 25 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3820e125..c0c6ad24 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,10 +6,11 @@ Apollo Java 2.4.0 ------------------ -* [Feature Support Kubernetes ConfigMap cache for Apollo java, golang client](https://github.com/apolloconfig/apollo-java/pull/79) * [Fix the Cannot enhance @Configuration bean definition issue](https://github.com/apolloconfig/apollo-java/pull/82) * [Feature openapi query namespace support not fill item](https://github.com/apolloconfig/apollo-java/pull/83) * [Add more observability in apollo config client](https://github.com/apolloconfig/apollo-java/pull/74) +* [Feature Support Kubernetes ConfigMap cache for Apollo java, golang client](https://github.com/apolloconfig/apollo-java/pull/79) + ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/4?closed=1) diff --git a/apollo-client/pom.xml b/apollo-client/pom.xml index 0bbeade9..1b51a8d9 100644 --- a/apollo-client/pom.xml +++ b/apollo-client/pom.xml @@ -73,6 +73,7 @@ io.kubernetes client-java + true diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index cbd94029..fc72cebd 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -54,25 +54,6 @@ public class K8sConfigMapConfigRepository extends AbstractConfigRepository private volatile ConfigRepository upstream; private volatile ConfigSourceType sourceType = ConfigSourceType.CONFIGMAP; - /** - * Constructor - * - * @param namespace the namespace - */ - public K8sConfigMapConfigRepository(String namespace) { - this(namespace, (ConfigRepository) null); - } - - public K8sConfigMapConfigRepository(String namespace, KubernetesManager kubernetesManager) { - this.namespace = namespace; - configUtil = ApolloInjector.getInstance(ConfigUtil.class); - k8sNamespace = configUtil.getK8sNamespace(); - this.kubernetesManager = kubernetesManager; - - this.setConfigMapKey(configUtil.getCluster(), namespace); - this.setConfigMapName(configUtil.getAppId(), false); - this.setUpstreamRepository(upstream); - } public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) { this.namespace = namespace; @@ -85,27 +66,22 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.setUpstreamRepository(upstream); } - public String getConfigMapKey() { - return configMapKey; - } - - public String getConfigMapName() { + String getConfigMapName() { return configMapName; } void setConfigMapKey(String cluster, String namespace) { - // cluster: 用户定义>idc>default + // cluster: User Definition >idc>default if (StringUtils.isBlank(cluster)) { - configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); + configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); return; } - configMapKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(cluster, namespace); + configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(cluster, namespace); } void setConfigMapName(String appId, boolean syncImmediately) { Preconditions.checkNotNull(appId, "AppId cannot be null"); configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; - // 初始化configmap this.checkConfigMapName(configMapName); if (syncImmediately) { this.sync(); @@ -141,7 +117,6 @@ public Properties getConfig() { } Properties result = propertiesFactory.getPropertiesInstance(); result.putAll(configMapProperties); - logger.info("configmap properties: {}", configMapProperties); return result; } @@ -152,7 +127,6 @@ public Properties getConfig() { */ @Override public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) { - // 设置上游数据源 if (upstreamConfigRepository == null) { return; } @@ -191,7 +165,6 @@ protected void sync() { Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex)); transaction.setStatus(ex); exception = ex; - throw new ApolloConfigException("Load config from Kubernetes ConfigMap failed!", ex); } finally { transaction.complete(); } @@ -210,7 +183,7 @@ public Properties loadFromK8sConfigMap() { String jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); if (jsonConfig == null) { // TODO 这样重试访问idc,default是否正确 - String retryKey = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join("default", namespace); + String retryKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, retryKey); } @@ -235,7 +208,6 @@ public boolean trySyncFromUpstream() { return false; } try { - logger.info("Start sync from the upstream data source, upstream.getConfig:{}, upstream.getSourceType():{}", upstream.getConfig(), upstream.getSourceType()); updateConfigMapProperties(upstream.getConfig(), upstream.getSourceType()); return true; } catch (Throwable ex) { @@ -248,7 +220,7 @@ public boolean trySyncFromUpstream() { private synchronized void updateConfigMapProperties(Properties newProperties, ConfigSourceType sourceType) { this.sourceType = sourceType; - if (newProperties.equals(configMapProperties)) { + if (newProperties == null || newProperties.equals(configMapProperties)) { return; } this.configMapProperties = newProperties; @@ -263,7 +235,7 @@ private synchronized void updateConfigMapProperties(Properties newProperties, Co */ @Override public void onRepositoryChange(String namespace, Properties newProperties) { - if (newProperties.equals(configMapProperties)) { + if (newProperties == null || newProperties.equals(configMapProperties)) { return; } Properties newFileProperties = propertiesFactory.getPropertiesInstance(); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index e37a7190..2e5cffcf 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -20,7 +20,6 @@ import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.Configuration; import io.kubernetes.client.openapi.models.*; import io.kubernetes.client.util.Config; import org.slf4j.Logger; @@ -41,7 +40,6 @@ public class KubernetesManager { public KubernetesManager() { try { client = Config.defaultClient(); - Configuration.setDefaultApiClient(client); coreV1Api = new CoreV1Api(client); } catch (Exception e) { String errorMessage = "Failed to initialize Kubernetes client: " + e.getMessage(); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java index f81f644b..d74b6c63 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spi/DefaultConfigFactory.java @@ -22,7 +22,18 @@ import com.ctrip.framework.apollo.PropertiesCompatibleConfigFile; import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; -import com.ctrip.framework.apollo.internals.*; +import com.ctrip.framework.apollo.internals.ConfigRepository; +import com.ctrip.framework.apollo.internals.DefaultConfig; +import com.ctrip.framework.apollo.internals.JsonConfigFile; +import com.ctrip.framework.apollo.internals.LocalFileConfigRepository; +import com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository; +import com.ctrip.framework.apollo.internals.PropertiesConfigFile; +import com.ctrip.framework.apollo.internals.RemoteConfigRepository; +import com.ctrip.framework.apollo.internals.TxtConfigFile; +import com.ctrip.framework.apollo.internals.XmlConfigFile; +import com.ctrip.framework.apollo.internals.YamlConfigFile; +import com.ctrip.framework.apollo.internals.YmlConfigFile; +import com.ctrip.framework.apollo.internals.K8sConfigMapConfigRepository; import com.ctrip.framework.apollo.util.ConfigUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 2c2c06ba..e2927f83 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -93,7 +93,7 @@ public void setUp() { kubernetesManager = new KubernetesManager(coreV1Api); MockInjector.setInstance(KubernetesManager.class, kubernetesManager); - configmapRepo = new K8sConfigMapConfigRepository(someNamespace, kubernetesManager); + //configmapRepo = new K8sConfigMapConfigRepository(someNamespace, kubernetesManager); MockInjector.setInstance(K8sConfigMapConfigRepository.class, configmapRepo); PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java index 9e5968d2..a2fea633 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java @@ -19,7 +19,8 @@ public interface ConfigConsts { String NAMESPACE_APPLICATION = "application"; String CLUSTER_NAME_DEFAULT = "default"; - String CLUSTER_NAMESPACE_SEPARATOR = "-"; + String CLUSTER_NAMESPACE_SEPARATOR = "+"; + String CONFIGMAP_KEY_SEPARATOR = "-"; String APOLLO_CONFIG_CACHE = "apollo-configcache-"; String APOLLO_CLUSTER_KEY = "apollo.cluster"; String APOLLO_META_KEY = "apollo.meta"; From f95ca86656470d1cab78ddb098459b3a47e39ba7 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Sun, 27 Oct 2024 21:59:29 +0800 Subject: [PATCH 18/25] fix ut --- .../K8sConfigMapConfigRepository.java | 4 + .../K8sConfigMapConfigRepositoryTest.java | 335 ++++++------------ 2 files changed, 113 insertions(+), 226 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index fc72cebd..42f10e59 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -66,6 +66,10 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.setUpstreamRepository(upstream); } + String getConfigMapKey() { + return configMapKey; + } + String getConfigMapName() { return configMapName; } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index e2927f83..5c3add57 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -1,278 +1,161 @@ -/* - * Copyright 2022 Apollo Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ package com.ctrip.framework.apollo.internals; -import com.ctrip.framework.apollo.kubernetes.KubernetesManager; import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.enums.ConfigSourceType; -import com.ctrip.framework.apollo.exceptions.ApolloConfigException; +import com.ctrip.framework.apollo.kubernetes.KubernetesManager; import com.ctrip.framework.apollo.util.ConfigUtil; -import com.ctrip.framework.apollo.util.factory.PropertiesFactory; -import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ObjectMeta; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.jupiter.api.BeforeEach; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.util.HashMap; import java.util.Map; import java.util.Properties; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; public class K8sConfigMapConfigRepositoryTest { + private static String someAppId = "someApp"; + private static String someCluster = "someCluster"; private String someNamespace = "default"; - private ConfigRepository upstreamRepo; - private Properties someProperties; - private static final String someAppId = "someApp"; private static final String someConfigmapName = "apollo-configcache-someApp"; - private static final String someCluster = "someCluster"; + private static final String defaultKey = "defaultKey"; private static final String defaultValue = "defaultValue"; - private ConfigSourceType someSourceType; - private V1ConfigMap configMap; - private CoreV1Api coreV1Api; + private static final String defaultJsonValue = "{\"id\":123,\"name\":\"John Doe\",\"email\":\"john.doe@example.com\"}"; - @InjectMocks + private ConfigRepository upstreamRepo; + private Properties someProperties; + private ConfigSourceType someSourceType = ConfigSourceType.LOCAL; + private V1ConfigMap configMap; + private Map data; private KubernetesManager kubernetesManager; - @Mock - private ConfigUtil configUtil; - private K8sConfigMapConfigRepository configmapRepo; + private K8sConfigMapConfigRepository k8sConfigMapConfigRepository; + @Before public void setUp() { + // mock configUtil + MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); + // mock kubernetesManager + kubernetesManager = mock(KubernetesManager.class); + MockInjector.setInstance(KubernetesManager.class, kubernetesManager); + + // mock upstream someProperties = new Properties(); someProperties.setProperty(defaultKey, defaultValue); + upstreamRepo = mock(ConfigRepository.class); + when(upstreamRepo.getConfig()).thenReturn(someProperties); + when(upstreamRepo.getSourceType()).thenReturn(someSourceType); - Map data = new HashMap<>(); - data.put(defaultKey, defaultValue); + // make comfigmap + data = new HashMap<>(); + data.put(defaultKey, defaultJsonValue); configMap = new V1ConfigMap() .metadata(new V1ObjectMeta().name(someAppId).namespace(someNamespace)) .data(data); - someSourceType = ConfigSourceType.LOCAL; - upstreamRepo = mock(ConfigRepository.class); - when(upstreamRepo.getConfig()).thenReturn(someProperties); - when(upstreamRepo.getSourceType()).thenReturn(someSourceType); - - // mock ConfigUtil - configUtil = mock(ConfigUtil.class); - when(configUtil.getAppId()).thenReturn("testAppId"); - when(configUtil.getCluster()).thenReturn("default"); - when(configUtil.getK8sNamespace()).thenReturn("default"); - when(configUtil.isPropertyKubernetesCacheEnabled()).thenReturn(true); - when(configUtil.isOverrideSystemProperties()).thenReturn(false); - when(configUtil.getDefaultLocalCacheDir()).thenReturn("/cache"); - when(configUtil.getMetaServerDomainName()).thenReturn("http://meta.server"); + k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository(someNamespace, upstreamRepo); - coreV1Api = mock(CoreV1Api.class); - MockInjector.setInstance(ConfigUtil.class, configUtil); - MockInjector.setInstance(CoreV1Api.class, coreV1Api); + } - kubernetesManager = new KubernetesManager(coreV1Api); - MockInjector.setInstance(KubernetesManager.class, kubernetesManager); - //configmapRepo = new K8sConfigMapConfigRepository(someNamespace, kubernetesManager); - MockInjector.setInstance(K8sConfigMapConfigRepository.class, configmapRepo); + @Test + public void testSetConfigMapKey() { + when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); + k8sConfigMapConfigRepository.setConfigMapKey(someCluster, someNamespace); + assertEquals(someCluster +"-"+ someNamespace, k8sConfigMapConfigRepository.getConfigMapKey()); + } - PropertiesFactory propertiesFactory = mock(PropertiesFactory.class); - when(propertiesFactory.getPropertiesInstance()).thenReturn(new Properties()); - MockInjector.setInstance(PropertiesFactory.class, propertiesFactory); + @Test + public void testSetConfigMapName() { + k8sConfigMapConfigRepository.setConfigMapName(someAppId, false); + assertEquals(someConfigmapName, k8sConfigMapConfigRepository.getConfigMapName()); } - @After - public void tearDown() throws Exception { - MockInjector.reset(); + /** + * 测试sync方法成功从上游数据源同步 + */ + @Test + public void testSyncSuccessFromUpstream() throws Throwable { + // arrange + k8sConfigMapConfigRepository.setUpstreamRepository(upstreamRepo); + + // act + k8sConfigMapConfigRepository.sync(); + + // assert + verify(upstreamRepo, times(1)).getConfig(); } -// @Test -// public void testSetConfigMapKey() { -// when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); -// configmapRepo.setConfigMapKey(someCluster, someNamespace); -// assertEquals(someCluster +"-"+ someNamespace, configmapRepo.getConfigMapKey()); -// } -// - @Test(expected = Exception.class) - public void testSetConfigMapNameWithNullAppId() { - configmapRepo.setConfigMapName(null, false); + /** + * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 + */ + @Test + public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { + // arrange + ConfigRepository upstream = mock(ConfigRepository.class); + when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); + k8sConfigMapConfigRepository.setUpstreamRepository(upstream); + when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn(data.get(defaultKey)); + + // act + k8sConfigMapConfigRepository.sync(); + + // assert + verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); } @Test - public void testSetConfigMapName() { - configmapRepo.setConfigMapName(someAppId, false); - assertEquals(someConfigmapName, configmapRepo.getConfigMapName()); + public void testGetConfig() { + // Arrange + Properties expectedProperties = new Properties(); + expectedProperties.setProperty(defaultKey, defaultValue); + when(upstreamRepo.getConfig()).thenReturn(expectedProperties); + // Act + Properties actualProperties = k8sConfigMapConfigRepository.getConfig(); + // Assert + assertNotNull(actualProperties); + assertEquals(defaultValue, actualProperties.getProperty(defaultKey)); + } + + @Test + public void testPersistConfigMap() { + // Arrange + Properties properties = new Properties(); + properties.setProperty(defaultKey, defaultValue); + // Act + k8sConfigMapConfigRepository.persistConfigMap(properties); + // Assert + verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); } + @Test + public void testOnRepositoryChange() { + // Arrange + Properties newProperties = new Properties(); + newProperties.setProperty(defaultKey, defaultValue); + // Act + k8sConfigMapConfigRepository.onRepositoryChange(someNamespace, newProperties); + // Assert + verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); + } -// @Test -// public void testOnRepositoryChange() throws Exception { -// RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); -// -// // 创建一个 LocalFileConfigRepository 实例,作为上游仓库 -// LocalFileConfigRepository upstreamRepo = mock(LocalFileConfigRepository.class); -// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); -// -// // 创建一个模拟的 KubernetesManager -// KubernetesManager mockKubernetesManager = mock(KubernetesManager.class); -// when(mockKubernetesManager.checkConfigMapExist(anyString(), anyString())).thenReturn(true); -// doNothing().when(mockKubernetesManager).createConfigMap(anyString(), anyString(), any()); -// -// K8sConfigMapConfigRepository k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository("someNamespace", upstreamRepo); -// k8sConfigMapConfigRepository.initialize(); -// -// // 设置本地缓存目录并添加监听器 -// k8sConfigMapConfigRepository.addChangeListener(someListener); -// k8sConfigMapConfigRepository.getConfig(); -// -// Properties anotherProperties = new Properties(); -// anotherProperties.put("anotherKey", "anotherValue"); -// -// ConfigSourceType anotherSourceType = ConfigSourceType.LOCAL; -// when(upstreamRepo.getSourceType()).thenReturn(anotherSourceType); -// -// // 调用 onRepositoryChange 方法,模拟仓库配置发生变化 -// k8sConfigMapConfigRepository.onRepositoryChange("someNamespace", anotherProperties); -// -// // 使用 ArgumentCaptor 捕获监听器的调用参数 -// final ArgumentCaptor captor = ArgumentCaptor.forClass(Properties.class); -// verify(someListener, times(1)).onRepositoryChange(eq("someNamespace"), captor.capture()); -// -// // 断言捕获的配置与 anotherProperties 相同 -// assertEquals(anotherProperties, captor.getValue()); -// // 断言 K8sConfigMapConfigRepository 的源类型更新为 anotherSourceType -// assertEquals(anotherSourceType, k8sConfigMapConfigRepository.getSourceType()); -// } + public static class MockConfigUtil extends ConfigUtil { + @Override + public String getAppId() { + return someAppId; + } - /** - * 测试persistConfigMap方法成功持久化配置信息 - */ -// @Test -// public void testPersistConfigMap() { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// doNothing().when(kubernetesManager).updateConfigMap(anyString(), anyString(), anyMap()); -// repo.persistConfigMap(someProperties); -// verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); -// } + @Override + public String getCluster() { + return someCluster; + } + } -// /** -// * 测试sync方法成功从上游数据源同步 -// */ -// @Test -// public void testSyncSuccessFromUpstream() throws Throwable { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// -// // arrange -// ConfigRepository upstream = mock(ConfigRepository.class); -// Properties upstreamProperties = new Properties(); -// upstreamProperties.setProperty("key", "value"); -// when(upstream.getConfig()).thenReturn(upstreamProperties); -// when(upstream.getSourceType()).thenReturn(ConfigSourceType.REMOTE); -// repo.setUpstreamRepository(upstream); -// -//// // mock KubernetesManager -//// when(kubernetesManager.createConfigMap(anyString(), anyString(), anyMap())) -//// .thenReturn(true); -//// setField(repo, "kubernetesManager", kubernetesManager); -// -// // act -// repo.sync(); -// -// // assert -// verify(upstream, times(1)).getConfig(); -// } -// -// @Test -// public void testSyncFromUpstreamWithFileStorage() throws Exception { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// -// -// Properties upstreamProperties = new Properties(); -// upstreamProperties.setProperty("key1", "value1"); -// -// when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); -// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.LOCAL); -// -// repo.sync(); -// -// Properties config = repo.getConfig(); -// assertEquals("value1", config.getProperty("key1")); -// assertEquals(ConfigSourceType.LOCAL, repo.getSourceType()); -// } -// -// @Test -// public void testSyncFromUpstreamWithRemote() throws Exception { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// -// Properties upstreamProperties = new Properties(); -// upstreamProperties.setProperty("key2", "value2"); -// -// when(upstreamRepo.getConfig()).thenReturn(upstreamProperties); -// when(upstreamRepo.getSourceType()).thenReturn(ConfigSourceType.REMOTE); -// -// repo.sync(); -// -// Properties config = repo.getConfig(); -// assertEquals("value2", config.getProperty("key2")); -// assertEquals(ConfigSourceType.REMOTE, repo.getSourceType()); -// } -// -// @Test -// public void testSyncFromK8s() throws Exception { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// -// Properties k8sProperties = new Properties(); -// k8sProperties.setProperty("key3", "value3"); -// -// when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())) -// .thenReturn(Base64.getEncoder().encodeToString("{\"key3\":\"value3\"}".getBytes())); -// -// repo.sync(); -// -// Properties config = repo.getConfig(); -// assertEquals("value3", config.getProperty("key3")); -// assertEquals(ConfigSourceType.CONFIGMAP, repo.getSourceType()); -// } -// -// -// /** -// * 测试sync方法从上游数据源同步失败,成功从Kubernetes的ConfigMap中加载 -// */ -// @Test -// public void testSyncFailFromUpstreamSuccessFromConfigMap() throws Throwable { -// K8sConfigMapConfigRepository repo = new K8sConfigMapConfigRepository(someNamespace); -// // arrange -// ConfigRepository upstream = mock(ConfigRepository.class); -// when(upstream.getConfig()).thenThrow(new RuntimeException("Upstream sync failed")); -// repo.setUpstreamRepository(upstream); -// when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn("encodedConfig"); -// -// // act -// repo.sync(); -// -// // assert -// verify(kubernetesManager, times(1)).getValueFromConfigMap(anyString(), anyString(), anyString()); -// } -// } From 7438b078278447aadef85c1a03fd4acecd6e89e9 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Mon, 28 Oct 2024 10:27:53 +0800 Subject: [PATCH 19/25] fix ut --- .../K8sConfigMapConfigRepository.java | 30 +++++++++++++++---- .../K8sConfigMapConfigRepositoryTest.java | 26 +++++++++++++++- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 42f10e59..8951c522 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -184,12 +184,7 @@ public Properties loadFromK8sConfigMap() { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); try { - String jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); - if (jsonConfig == null) { - // TODO 这样重试访问idc,default是否正确 - String retryKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, retryKey); - } + String jsonConfig = loadConfigFromK8sWithRetry(); // Convert jsonConfig to properties Properties properties = propertiesFactory.getPropertiesInstance(); @@ -207,6 +202,29 @@ public Properties loadFromK8sConfigMap() { } } + private String loadConfigFromK8sWithRetry() { + String jsonConfig = null; + + // Try to load from the specified cluster + if (!configMapKey.equals("default")) { + jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); + } + + // Try to load from the data center cluster + if (StringUtils.isBlank(jsonConfig) && !configUtil.getDataCenter().equals(configMapKey)) { + String dataCenterKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(configUtil.getDataCenter(), namespace); + jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, dataCenterKey); + } + + // Fallback to the default cluster + if (StringUtils.isBlank(jsonConfig)) { + String defaultKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); + jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, defaultKey); + } + + return jsonConfig; + } + public boolean trySyncFromUpstream() { if (upstream == null) { return false; diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 5c3add57..7cb3392d 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.ctrip.framework.apollo.internals; import com.ctrip.framework.apollo.build.MockInjector; @@ -60,7 +76,6 @@ public void setUp() { .data(data); k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository(someNamespace, upstreamRepo); - } @@ -146,6 +161,15 @@ public void testOnRepositoryChange() { verify(kubernetesManager, times(1)).updateConfigMap(anyString(), anyString(), anyMap()); } + @Test + public void testLoadFromK8sConfigMapSuccess() { + when(kubernetesManager.getValueFromConfigMap(anyString(), anyString(), anyString())).thenReturn(defaultJsonValue); + + Properties properties = k8sConfigMapConfigRepository.loadFromK8sConfigMap(); + + assertNotNull(properties); + } + public static class MockConfigUtil extends ConfigUtil { @Override public String getAppId() { From 0c66c5964c9ab5cad00f2f1b3bcae8c8286b55c5 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Tue, 29 Oct 2024 20:05:54 +0800 Subject: [PATCH 20/25] fix ut --- .../apollo/internals/K8sConfigMapConfigRepository.java | 7 +++---- .../framework/apollo/kubernetes/KubernetesManager.java | 9 +++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index 8951c522..d07f5b61 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -53,6 +53,7 @@ public class K8sConfigMapConfigRepository extends AbstractConfigRepository private volatile Properties configMapProperties; private volatile ConfigRepository upstream; private volatile ConfigSourceType sourceType = ConfigSourceType.CONFIGMAP; + private static final Gson GSON = new Gson(); public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) { @@ -189,9 +190,8 @@ public Properties loadFromK8sConfigMap() { // Convert jsonConfig to properties Properties properties = propertiesFactory.getPropertiesInstance(); if (jsonConfig != null && !jsonConfig.isEmpty()) { - Gson gson = new Gson(); Type type = new TypeToken>() {}.getType(); - Map configMap = gson.fromJson(jsonConfig, type); + Map configMap = GSON.fromJson(jsonConfig, type); configMap.forEach(properties::setProperty); } return properties; @@ -272,8 +272,7 @@ public void persistConfigMap(Properties properties) { transaction.addData("k8sNamespace", k8sNamespace); try { // Convert properties to a JSON string using Gson - Gson gson = new Gson(); - String jsonConfig = gson.toJson(properties); + String jsonConfig = GSON.toJson(properties); Map data = new HashMap<>(); data.put(configMapKey, jsonConfig); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index 2e5cffcf..bdd57df9 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -20,7 +20,8 @@ import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,7 +94,7 @@ public String createConfigMap(String k8sNamespace, String name, Map data) { if (StringUtils.isEmpty(k8sNamespace) || StringUtils.isEmpty(name)) { logger.error("Parameters can not be null or empty: k8sNamespace={}, name={}", k8sNamespace, name); From f7ff6d9840db655a53e7b942b46a9f97e645eea6 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 30 Oct 2024 13:36:43 +0800 Subject: [PATCH 21/25] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=98=A8?= =?UTF-8?q?=E6=99=9A=E8=AE=A8=E8=AE=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../K8sConfigMapConfigRepository.java | 49 ++++--------------- .../apollo/kubernetes/KubernetesManager.java | 33 ++++++++++--- .../framework/apollo/util/ConfigUtil.java | 4 +- .../apollo/util/escape/EscapeUtil.java | 35 +++++++++++++ .../K8sConfigMapConfigRepositoryTest.java | 47 +++++++++++++----- .../framework/apollo/util/ConfigUtilTest.java | 12 +++++ .../framework/apollo/core/ConfigConsts.java | 1 - 7 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index d07f5b61..5b5f9124 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -27,7 +27,7 @@ import com.ctrip.framework.apollo.tracer.spi.Transaction; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ExceptionUtil; -import com.google.common.base.Joiner; +import com.ctrip.framework.apollo.util.escape.EscapeUtil; import com.google.common.base.Preconditions; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -67,24 +67,16 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.setUpstreamRepository(upstream); } - String getConfigMapKey() { - return configMapKey; - } - - String getConfigMapName() { - return configMapName; - } - - void setConfigMapKey(String cluster, String namespace) { + private void setConfigMapKey(String cluster, String namespace) { // cluster: User Definition >idc>default if (StringUtils.isBlank(cluster)) { - configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); + configMapKey = EscapeUtil.createConfigMapKey("default", namespace); return; } - configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(cluster, namespace); + configMapKey = EscapeUtil.createConfigMapKey(cluster, namespace); } - void setConfigMapName(String appId, boolean syncImmediately) { + private void setConfigMapName(String appId, boolean syncImmediately) { Preconditions.checkNotNull(appId, "AppId cannot be null"); configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; this.checkConfigMapName(configMapName); @@ -181,11 +173,11 @@ protected void sync() { } } - public Properties loadFromK8sConfigMap() { + Properties loadFromK8sConfigMap() { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); try { - String jsonConfig = loadConfigFromK8sWithRetry(); + String jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); // Convert jsonConfig to properties Properties properties = propertiesFactory.getPropertiesInstance(); @@ -202,30 +194,7 @@ public Properties loadFromK8sConfigMap() { } } - private String loadConfigFromK8sWithRetry() { - String jsonConfig = null; - - // Try to load from the specified cluster - if (!configMapKey.equals("default")) { - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); - } - - // Try to load from the data center cluster - if (StringUtils.isBlank(jsonConfig) && !configUtil.getDataCenter().equals(configMapKey)) { - String dataCenterKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(configUtil.getDataCenter(), namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, dataCenterKey); - } - - // Fallback to the default cluster - if (StringUtils.isBlank(jsonConfig)) { - String defaultKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, defaultKey); - } - - return jsonConfig; - } - - public boolean trySyncFromUpstream() { + private boolean trySyncFromUpstream() { if (upstream == null) { return false; } @@ -266,7 +235,7 @@ public void onRepositoryChange(String namespace, Properties newProperties) { this.fireRepositoryChange(namespace, newProperties); } - public void persistConfigMap(Properties properties) { + void persistConfigMap(Properties properties) { Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); transaction.addData("configMapName", configMapName); transaction.addData("k8sNamespace", k8sNamespace); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index bdd57df9..08729e5f 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.kubernetes; import com.ctrip.framework.apollo.core.utils.StringUtils; +import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; @@ -36,7 +37,7 @@ public class KubernetesManager { private ApiClient client; private CoreV1Api coreV1Api; - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private static final Logger logger = LoggerFactory.getLogger(KubernetesManager.class); public KubernetesManager() { try { @@ -53,7 +54,7 @@ public KubernetesManager(CoreV1Api coreV1Api) { this.coreV1Api = coreV1Api; } - public V1ConfigMap buildConfigMap(String name, String namespace, Map data) { + private V1ConfigMap buildConfigMap(String name, String namespace, Map data) { V1ObjectMeta metadata = new V1ObjectMeta() .name(name) .namespace(namespace); @@ -124,13 +125,12 @@ public String getValueFromConfigMap(String k8sNamespace, String name, String key * @return config map name */ // Set the retry times using the client retry mechanism (CAS) - public boolean updateConfigMap(String k8sNamespace, String name, Map data) { + public boolean updateConfigMap(String k8sNamespace, String name, Map data) throws ApiException { if (StringUtils.isEmpty(k8sNamespace) || StringUtils.isEmpty(name)) { logger.error("Parameters can not be null or empty: k8sNamespace={}, name={}", k8sNamespace, name); return false; } - // retry int maxRetries = 5; int retryCount = 0; long waitTime = 100; @@ -138,7 +138,21 @@ public boolean updateConfigMap(String k8sNamespace, String name, Map existingData = configmap.getData(); + + // Determine if the data contains its own kv and de-weight it + boolean containsEntry = data.entrySet().stream() + .allMatch(entry -> entry.getValue().equals(existingData.get(entry.getKey()))); + + if (containsEntry) { + logger.info("Data is identical or already contains the entry, no update needed."); + return true; + } + + // Add new entries to the existing data + existingData.putAll(data); + configmap.setData(existingData); + coreV1Api.replaceNamespacedConfigMap(name, k8sNamespace, configmap, null, null, null, null); return true; } catch (ApiException e) { @@ -146,17 +160,22 @@ public boolean updateConfigMap(String k8sNamespace, String name, Map(); data.put(defaultKey, defaultJsonValue); configMap = new V1ConfigMap() @@ -78,20 +81,40 @@ public void setUp() { k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository(someNamespace, upstreamRepo); } + // TODO 直接mock manager中的参数 + /** + * 测试setConfigMapKey方法,当cluster和namespace都为正常值时 + */ @Test - public void testSetConfigMapKey() { - when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); - k8sConfigMapConfigRepository.setConfigMapKey(someCluster, someNamespace); - assertEquals(someCluster +"-"+ someNamespace, k8sConfigMapConfigRepository.getConfigMapKey()); - } + public void testSetConfigMapKeyUnderNormalConditions() throws Throwable { + // arrange + String cluster = "testCluster"; + String namespace = "test_Namespace_1"; + String escapedKey = "testCluster___test__Namespace__1"; - @Test - public void testSetConfigMapName() { - k8sConfigMapConfigRepository.setConfigMapName(someAppId, false); - assertEquals(someConfigmapName, k8sConfigMapConfigRepository.getConfigMapName()); + // act + ReflectionTestUtils.invokeMethod(k8sConfigMapConfigRepository, "setConfigMapKey", cluster, namespace); + + // assert + String expectedConfigMapKey = EscapeUtil.createConfigMapKey(cluster, namespace); + assertEquals(escapedKey, ReflectionTestUtils.getField(k8sConfigMapConfigRepository, "configMapKey")); + assertEquals(expectedConfigMapKey, ReflectionTestUtils.getField(k8sConfigMapConfigRepository, "configMapKey")); } +// @Test +// public void testSetConfigMapKey() { +// when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); +// k8sConfigMapConfigRepository.setConfigMapKey(someCluster, someNamespace); +// assertEquals(someCluster +"-"+ someNamespace, k8sConfigMapConfigRepository.getConfigMapKey()); +// } +// +// @Test +// public void testSetConfigMapName() { +// k8sConfigMapConfigRepository.setConfigMapName(someAppId, false); +// assertEquals(someConfigmapName, k8sConfigMapConfigRepository.getConfigMapName()); +// } + /** * 测试sync方法成功从上游数据源同步 */ @@ -140,7 +163,7 @@ public void testGetConfig() { } @Test - public void testPersistConfigMap() { + public void testPersistConfigMap() throws ApiException { // Arrange Properties properties = new Properties(); properties.setProperty(defaultKey, defaultValue); @@ -151,7 +174,7 @@ public void testPersistConfigMap() { } @Test - public void testOnRepositoryChange() { + public void testOnRepositoryChange() throws ApiException { // Arrange Properties newProperties = new Properties(); newProperties.setProperty(defaultKey, defaultValue); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index f173b072..e9f8ea1e 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -48,6 +48,7 @@ public void tearDown() throws Exception { System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); System.clearProperty(ApolloClientSystemConsts.APOLLO_PROPERTY_NAMES_CACHE_ENABLE); System.clearProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_NAMESPACE); + System.clearProperty(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE); } @Test @@ -262,6 +263,17 @@ public void testK8sNamespaceWithDefault() { assertEquals(ConfigConsts.KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE_DEFAULT, configUtil.getK8sNamespace()); } + @Test + public void testKubernetesCacheEnabledWithSystemProperty() { + boolean someKubernetesCacheEnabled = true; + + System.setProperty(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE, String.valueOf(someKubernetesCacheEnabled)); + + ConfigUtil configUtil = new ConfigUtil(); + + assertTrue(configUtil.isPropertyKubernetesCacheEnabled()); + } + @Test public void testCustomizePropertiesOrdered() { boolean propertiesOrdered = true; diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java index a2fea633..621c2c97 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java @@ -20,7 +20,6 @@ public interface ConfigConsts { String NAMESPACE_APPLICATION = "application"; String CLUSTER_NAME_DEFAULT = "default"; String CLUSTER_NAMESPACE_SEPARATOR = "+"; - String CONFIGMAP_KEY_SEPARATOR = "-"; String APOLLO_CONFIG_CACHE = "apollo-configcache-"; String APOLLO_CLUSTER_KEY = "apollo.cluster"; String APOLLO_META_KEY = "apollo.meta"; From 84d10fd1993ee9eb6ba1ff57825e4765e2466810 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 30 Oct 2024 14:15:11 +0800 Subject: [PATCH 22/25] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=98=A8?= =?UTF-8?q?=E6=99=9A=E8=AE=A8=E8=AE=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apollo/kubernetes/KubernetesManager.java | 7 ++++++- .../framework/apollo/util/escape/EscapeUtil.java | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index 08729e5f..532592cd 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -139,10 +140,14 @@ public boolean updateConfigMap(String k8sNamespace, String name, Map existingData = configmap.getData(); + if (existingData == null) { + existingData = new HashMap<>(); + } // Determine if the data contains its own kv and de-weight it + Map finalExistingData = existingData; boolean containsEntry = data.entrySet().stream() - .allMatch(entry -> entry.getValue().equals(existingData.get(entry.getKey()))); + .allMatch(entry -> entry.getValue().equals(finalExistingData.get(entry.getKey()))); if (containsEntry) { logger.info("Data is identical or already contains the entry, no update needed."); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java index 7125c54f..8a499115 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java @@ -21,15 +21,22 @@ */ public class EscapeUtil { + private static final String SINGLE_UNDERSCORE = "_"; + private static final String DOUBLE_UNDERSCORE = "__"; + private static final String TRIPLE_UNDERSCORE = "___"; + // Escapes a single underscore in a namespace public static String escapeNamespace(String namespace) { - return namespace.replace("_", "__"); + if (namespace == null || namespace.isEmpty()) { + throw new IllegalArgumentException("Namespace cannot be null or empty"); + } + return namespace.replace(SINGLE_UNDERSCORE, DOUBLE_UNDERSCORE); } // Concatenate the cluster and the escaped namespace, using three underscores as delimiters public static String createConfigMapKey(String cluster, String namespace) { String escapedNamespace = escapeNamespace(namespace); - return cluster + "___" + escapedNamespace; + return String.join(TRIPLE_UNDERSCORE, cluster, escapedNamespace); } } From 41672e677fc03435ee54360e4218a887cc852b51 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 30 Oct 2024 14:20:35 +0800 Subject: [PATCH 23/25] fix --- .../K8sConfigMapConfigRepositoryTest.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java index 30e59993..f0c02071 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepositoryTest.java @@ -81,8 +81,6 @@ public void setUp() { k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository(someNamespace, upstreamRepo); } - // TODO 直接mock manager中的参数 - /** * 测试setConfigMapKey方法,当cluster和namespace都为正常值时 */ @@ -102,19 +100,6 @@ public void testSetConfigMapKeyUnderNormalConditions() throws Throwable { assertEquals(expectedConfigMapKey, ReflectionTestUtils.getField(k8sConfigMapConfigRepository, "configMapKey")); } -// @Test -// public void testSetConfigMapKey() { -// when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); -// k8sConfigMapConfigRepository.setConfigMapKey(someCluster, someNamespace); -// assertEquals(someCluster +"-"+ someNamespace, k8sConfigMapConfigRepository.getConfigMapKey()); -// } -// -// @Test -// public void testSetConfigMapName() { -// k8sConfigMapConfigRepository.setConfigMapName(someAppId, false); -// assertEquals(someConfigmapName, k8sConfigMapConfigRepository.getConfigMapName()); -// } - /** * 测试sync方法成功从上游数据源同步 */ From 03aa3ade678f01aee2346f5426210fbd0ef355e3 Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 30 Oct 2024 13:36:43 +0800 Subject: [PATCH 24/25] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=98=A8?= =?UTF-8?q?=E6=99=9A=E8=AE=A8=E8=AE=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../K8sConfigMapConfigRepository.java | 49 ++++--------------- .../apollo/kubernetes/KubernetesManager.java | 38 +++++++++++--- .../framework/apollo/util/ConfigUtil.java | 4 +- .../apollo/util/escape/EscapeUtil.java | 42 ++++++++++++++++ .../K8sConfigMapConfigRepositoryTest.java | 34 ++++++++----- .../framework/apollo/util/ConfigUtilTest.java | 12 +++++ .../framework/apollo/core/ConfigConsts.java | 1 - 7 files changed, 117 insertions(+), 63 deletions(-) create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/util/escape/EscapeUtil.java diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java index d07f5b61..5b5f9124 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/K8sConfigMapConfigRepository.java @@ -27,7 +27,7 @@ import com.ctrip.framework.apollo.tracer.spi.Transaction; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ExceptionUtil; -import com.google.common.base.Joiner; +import com.ctrip.framework.apollo.util.escape.EscapeUtil; import com.google.common.base.Preconditions; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -67,24 +67,16 @@ public K8sConfigMapConfigRepository(String namespace, ConfigRepository upstream) this.setUpstreamRepository(upstream); } - String getConfigMapKey() { - return configMapKey; - } - - String getConfigMapName() { - return configMapName; - } - - void setConfigMapKey(String cluster, String namespace) { + private void setConfigMapKey(String cluster, String namespace) { // cluster: User Definition >idc>default if (StringUtils.isBlank(cluster)) { - configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); + configMapKey = EscapeUtil.createConfigMapKey("default", namespace); return; } - configMapKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(cluster, namespace); + configMapKey = EscapeUtil.createConfigMapKey(cluster, namespace); } - void setConfigMapName(String appId, boolean syncImmediately) { + private void setConfigMapName(String appId, boolean syncImmediately) { Preconditions.checkNotNull(appId, "AppId cannot be null"); configMapName = ConfigConsts.APOLLO_CONFIG_CACHE + appId; this.checkConfigMapName(configMapName); @@ -181,11 +173,11 @@ protected void sync() { } } - public Properties loadFromK8sConfigMap() { + Properties loadFromK8sConfigMap() { Preconditions.checkNotNull(configMapName, "ConfigMap name cannot be null"); try { - String jsonConfig = loadConfigFromK8sWithRetry(); + String jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); // Convert jsonConfig to properties Properties properties = propertiesFactory.getPropertiesInstance(); @@ -202,30 +194,7 @@ public Properties loadFromK8sConfigMap() { } } - private String loadConfigFromK8sWithRetry() { - String jsonConfig = null; - - // Try to load from the specified cluster - if (!configMapKey.equals("default")) { - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, configMapKey); - } - - // Try to load from the data center cluster - if (StringUtils.isBlank(jsonConfig) && !configUtil.getDataCenter().equals(configMapKey)) { - String dataCenterKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join(configUtil.getDataCenter(), namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, dataCenterKey); - } - - // Fallback to the default cluster - if (StringUtils.isBlank(jsonConfig)) { - String defaultKey = Joiner.on(ConfigConsts.CONFIGMAP_KEY_SEPARATOR).join("default", namespace); - jsonConfig = kubernetesManager.getValueFromConfigMap(k8sNamespace, configMapName, defaultKey); - } - - return jsonConfig; - } - - public boolean trySyncFromUpstream() { + private boolean trySyncFromUpstream() { if (upstream == null) { return false; } @@ -266,7 +235,7 @@ public void onRepositoryChange(String namespace, Properties newProperties) { this.fireRepositoryChange(namespace, newProperties); } - public void persistConfigMap(Properties properties) { + void persistConfigMap(Properties properties) { Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "persistK8sConfigMap"); transaction.addData("configMapName", configMapName); transaction.addData("k8sNamespace", k8sNamespace); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index bdd57df9..532592cd 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.kubernetes; import com.ctrip.framework.apollo.core.utils.StringUtils; +import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.CoreV1Api; @@ -27,6 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -36,7 +38,7 @@ public class KubernetesManager { private ApiClient client; private CoreV1Api coreV1Api; - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private static final Logger logger = LoggerFactory.getLogger(KubernetesManager.class); public KubernetesManager() { try { @@ -53,7 +55,7 @@ public KubernetesManager(CoreV1Api coreV1Api) { this.coreV1Api = coreV1Api; } - public V1ConfigMap buildConfigMap(String name, String namespace, Map data) { + private V1ConfigMap buildConfigMap(String name, String namespace, Map data) { V1ObjectMeta metadata = new V1ObjectMeta() .name(name) .namespace(namespace); @@ -124,13 +126,12 @@ public String getValueFromConfigMap(String k8sNamespace, String name, String key * @return config map name */ // Set the retry times using the client retry mechanism (CAS) - public boolean updateConfigMap(String k8sNamespace, String name, Map data) { + public boolean updateConfigMap(String k8sNamespace, String name, Map data) throws ApiException { if (StringUtils.isEmpty(k8sNamespace) || StringUtils.isEmpty(name)) { logger.error("Parameters can not be null or empty: k8sNamespace={}, name={}", k8sNamespace, name); return false; } - // retry int maxRetries = 5; int retryCount = 0; long waitTime = 100; @@ -138,7 +139,25 @@ public boolean updateConfigMap(String k8sNamespace, String name, Map existingData = configmap.getData(); + if (existingData == null) { + existingData = new HashMap<>(); + } + + // Determine if the data contains its own kv and de-weight it + Map finalExistingData = existingData; + boolean containsEntry = data.entrySet().stream() + .allMatch(entry -> entry.getValue().equals(finalExistingData.get(entry.getKey()))); + + if (containsEntry) { + logger.info("Data is identical or already contains the entry, no update needed."); + return true; + } + + // Add new entries to the existing data + existingData.putAll(data); + configmap.setData(existingData); + coreV1Api.replaceNamespacedConfigMap(name, k8sNamespace, configmap, null, null, null, null); return true; } catch (ApiException e) { @@ -146,17 +165,22 @@ public boolean updateConfigMap(String k8sNamespace, String name, Map(); data.put(defaultKey, defaultJsonValue); configMap = new V1ConfigMap() @@ -78,18 +81,23 @@ public void setUp() { k8sConfigMapConfigRepository = new K8sConfigMapConfigRepository(someNamespace, upstreamRepo); } - + /** + * 测试setConfigMapKey方法,当cluster和namespace都为正常值时 + */ @Test - public void testSetConfigMapKey() { - when(kubernetesManager.createConfigMap(anyString(), anyString(), any())).thenReturn("someAppId"); - k8sConfigMapConfigRepository.setConfigMapKey(someCluster, someNamespace); - assertEquals(someCluster +"-"+ someNamespace, k8sConfigMapConfigRepository.getConfigMapKey()); - } + public void testSetConfigMapKeyUnderNormalConditions() throws Throwable { + // arrange + String cluster = "testCluster"; + String namespace = "test_Namespace_1"; + String escapedKey = "testCluster___test__Namespace__1"; - @Test - public void testSetConfigMapName() { - k8sConfigMapConfigRepository.setConfigMapName(someAppId, false); - assertEquals(someConfigmapName, k8sConfigMapConfigRepository.getConfigMapName()); + // act + ReflectionTestUtils.invokeMethod(k8sConfigMapConfigRepository, "setConfigMapKey", cluster, namespace); + + // assert + String expectedConfigMapKey = EscapeUtil.createConfigMapKey(cluster, namespace); + assertEquals(escapedKey, ReflectionTestUtils.getField(k8sConfigMapConfigRepository, "configMapKey")); + assertEquals(expectedConfigMapKey, ReflectionTestUtils.getField(k8sConfigMapConfigRepository, "configMapKey")); } /** @@ -140,7 +148,7 @@ public void testGetConfig() { } @Test - public void testPersistConfigMap() { + public void testPersistConfigMap() throws ApiException { // Arrange Properties properties = new Properties(); properties.setProperty(defaultKey, defaultValue); @@ -151,7 +159,7 @@ public void testPersistConfigMap() { } @Test - public void testOnRepositoryChange() { + public void testOnRepositoryChange() throws ApiException { // Arrange Properties newProperties = new Properties(); newProperties.setProperty(defaultKey, defaultValue); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index f173b072..e9f8ea1e 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -48,6 +48,7 @@ public void tearDown() throws Exception { System.clearProperty(PropertiesFactory.APOLLO_PROPERTY_ORDER_ENABLE); System.clearProperty(ApolloClientSystemConsts.APOLLO_PROPERTY_NAMES_CACHE_ENABLE); System.clearProperty(ApolloClientSystemConsts.APOLLO_CACHE_KUBERNETES_NAMESPACE); + System.clearProperty(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE); } @Test @@ -262,6 +263,17 @@ public void testK8sNamespaceWithDefault() { assertEquals(ConfigConsts.KUBERNETES_CACHE_CONFIG_MAP_NAMESPACE_DEFAULT, configUtil.getK8sNamespace()); } + @Test + public void testKubernetesCacheEnabledWithSystemProperty() { + boolean someKubernetesCacheEnabled = true; + + System.setProperty(ApolloClientSystemConsts.APOLLO_KUBERNETES_CACHE_ENABLE, String.valueOf(someKubernetesCacheEnabled)); + + ConfigUtil configUtil = new ConfigUtil(); + + assertTrue(configUtil.isPropertyKubernetesCacheEnabled()); + } + @Test public void testCustomizePropertiesOrdered() { boolean propertiesOrdered = true; diff --git a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java index a2fea633..621c2c97 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java +++ b/apollo-core/src/main/java/com/ctrip/framework/apollo/core/ConfigConsts.java @@ -20,7 +20,6 @@ public interface ConfigConsts { String NAMESPACE_APPLICATION = "application"; String CLUSTER_NAME_DEFAULT = "default"; String CLUSTER_NAMESPACE_SEPARATOR = "+"; - String CONFIGMAP_KEY_SEPARATOR = "-"; String APOLLO_CONFIG_CACHE = "apollo-configcache-"; String APOLLO_CLUSTER_KEY = "apollo.cluster"; String APOLLO_META_KEY = "apollo.meta"; From b8392ca48b9ce73ec16da717ea45c987af40d66a Mon Sep 17 00:00:00 2001 From: dyx1234 <2060307490@qq.com> Date: Wed, 30 Oct 2024 21:03:23 +0800 Subject: [PATCH 25/25] fix --- .../framework/apollo/kubernetes/KubernetesManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java index 532592cd..4cf96f38 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/kubernetes/KubernetesManager.java @@ -35,11 +35,11 @@ @Service public class KubernetesManager { + private static final Logger logger = LoggerFactory.getLogger(KubernetesManager.class); + private ApiClient client; private CoreV1Api coreV1Api; - private static final Logger logger = LoggerFactory.getLogger(KubernetesManager.class); - public KubernetesManager() { try { client = Config.defaultClient(); @@ -201,7 +201,7 @@ public boolean checkConfigMapExist(String k8sNamespace, String configMapName) { return true; } catch (Exception e) { // configmap not exist - logger.error("Error checking ConfigMap existence: {}", e.getMessage(), e); + logger.info("ConfigMap not existence"); return false; } }