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] 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