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