From 8308a9f172ff0b8a63b9ac55f1f888b2e185de9e Mon Sep 17 00:00:00 2001 From: Haotian Zhang <928016560@qq.com> Date: Wed, 4 Mar 2026 15:38:11 +0800 Subject: [PATCH] feat: Add config address to report client url conversion. (#1795) Signed-off-by: Haotian Zhang <928016560@qq.com> --- .gitignore | 2 + CHANGELOG.md | 1 + .../polaris/config/ConfigurationModifier.java | 34 +++ .../PolarisConfigDataLocationResolver.java | 24 +- .../config/ConfigurationModifierTest.java | 234 ++++++++++++++++++ 5 files changed, 286 insertions(+), 9 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/ConfigurationModifierTest.java diff --git a/.gitignore b/.gitignore index 3a84537ca..65975da2d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ lib/ applog/ .arthas/ .codebuddy/ +CLAUDE.md +.claude/ # Maven ignore .flattened-pom.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 06c1f8175..472b8c435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ # Change Log --- +- [feat: Add config address to report client url conversion.](https://github.com/Tencent/spring-cloud-tencent/pull/1795) diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java index a82f9a982..0202486b1 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java @@ -34,6 +34,7 @@ import com.tencent.polaris.api.utils.StringUtils; import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.factory.config.configuration.ConfigFilterConfigImpl; import com.tencent.polaris.factory.config.configuration.ConnectorConfigImpl; +import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,6 +126,16 @@ public class ConfigurationModifier implements PolarisConfigurationConfigModifier connectorConfig.setEmptyProtectionEnable(polarisConfigProperties.isEmptyProtectionEnabled()); connectorConfig.setEmptyProtectionExpiredInterval(polarisConfigProperties.getEmptyProtectionExpiredInterval()); + ServerConnectorConfigImpl reportClientConnectorConfig = configuration.getGlobal().getServerConnector(); + if (StringUtils.isNotBlank(polarisContextProperties.getAddress())) { + reportClientConnectorConfig.setAddresses(AddressUtils.parseAddressList(polarisContextProperties.getAddress())); + } + else { + reportClientConnectorConfig.setAddresses(resolvePolarisAddressFromConfigAddress(polarisConfigProperties.getAddress())); + } + reportClientConnectorConfig.setLbPolicy(polarisContextProperties.getAddressLbPolicy()); + reportClientConnectorConfig.setServerSwitchInterval(polarisContextProperties.getServerSwitchInterval()); + LOGGER.info("[SCT] Run spring cloud tencent config in polaris data source."); } @@ -161,6 +172,29 @@ public class ConfigurationModifier implements PolarisConfigurationConfigModifier return configAddresses; } + private List resolvePolarisAddressFromConfigAddress(String configAddress) { + if (StringUtils.isEmpty(configAddress)) { + return null; + } + + List configAddresses = AddressUtils.parseAddressList(configAddress); + List polarisAddresses = new ArrayList<>(configAddresses.size()); + + for (String address : configAddresses) { + if (StringUtils.isNotBlank(address)) { + int pos = address.lastIndexOf(":"); + if (pos != -1) { + polarisAddresses.add(address.substring(0, pos) + ":8091"); + } + else { + polarisAddresses.add(address); + } + } + } + + return polarisAddresses; + } + private void checkAddressAccessible(List configAddresses) { // check address can connect configAddresses.forEach(address -> { diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLocationResolver.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLocationResolver.java index 5dc08f9f7..4b9e8abe4 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLocationResolver.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLocationResolver.java @@ -273,16 +273,22 @@ public class PolarisConfigDataLocationResolver implements PolarisContextProperties polarisContextProperties) { ConfigurableBootstrapContext bootstrapContext = resolverContext.getBootstrapContext(); if (!bootstrapContext.isRegistered(SDKContext.class)) { - SDKContext sdkContext = sdkContext(resolverContext, - polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties); - if (sdkContext.getConfig() instanceof ConfigurationImpl) { - // not init reporter when creating config data temp SDK context. - ((ConfigurationImpl) sdkContext.getConfig()).getGlobal().getStatReporter().setEnable(false); - // not init circuit breaker when creating config data temp SDK context. - ((ConfigurationImpl) sdkContext.getConfig()).getConsumer().getCircuitBreaker().setEnable(false); + SDKContext sdkContext; + try { + sdkContext = PolarisConfigSDKContextManager.innerGetConfigSDKContext(); + } + catch (IllegalArgumentException e) { + sdkContext = sdkContext(resolverContext, + polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties); + if (sdkContext.getConfig() instanceof ConfigurationImpl) { + // not init reporter when creating config data temp SDK context. + ((ConfigurationImpl) sdkContext.getConfig()).getGlobal().getStatReporter().setEnable(false); + // not init circuit breaker when creating config data temp SDK context. + ((ConfigurationImpl) sdkContext.getConfig()).getConsumer().getCircuitBreaker().setEnable(false); + } + sdkContext.init(); + PolarisConfigSDKContextManager.setConfigSDKContext(sdkContext); } - sdkContext.init(); - PolarisConfigSDKContextManager.setConfigSDKContext(sdkContext); } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/ConfigurationModifierTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/ConfigurationModifierTest.java new file mode 100644 index 000000000..4ae0d3a1b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/ConfigurationModifierTest.java @@ -0,0 +1,234 @@ +/* +* Tencent is pleased to support the open source community by making spring-cloud-tencent available. +* +* Copyright (C) 2021 Tencent. All rights reserved. +* +* Licensed under the BSD 3-Clause License (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* 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.tencent.cloud.polaris.config; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.tencent.cloud.common.util.AddressUtils; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties; +import com.tencent.cloud.polaris.context.config.PolarisContextProperties; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; + +/** +* Test for {@link ConfigurationModifier}. +* +* @author Haotian Zhang +*/ +@DisplayName("ConfigurationModifier Test") +@ExtendWith(MockitoExtension.class) +class ConfigurationModifierTest { + + @Mock + private PolarisConfigProperties polarisConfigProperties; + + @Mock + private PolarisCryptoConfigProperties polarisCryptoConfigProperties; + + @Mock + private PolarisContextProperties polarisContextProperties; + + private ConfigurationModifier configurationModifier; + + @BeforeEach + void setUp() { + configurationModifier = new ConfigurationModifier( + polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties); + } + + /** + * Invoke the private method resolvePolarisAddressFromConfigAddress via reflection. + */ + @SuppressWarnings("unchecked") + private List invokeResolvePolarisAddressFromConfigAddress(String configAddress) throws Exception { + Method method = ConfigurationModifier.class.getDeclaredMethod( + "resolvePolarisAddressFromConfigAddress", String.class); + method.setAccessible(true); + return (List) method.invoke(configurationModifier, configAddress); + } + + /** + * Test resolvePolarisAddressFromConfigAddress when configAddress is null. + * Scenario: pass null as configAddress. + * Expect: return null directly without calling AddressUtils. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should return null when configAddress is null") + @Test + void testResolvePolarisAddressFromConfigAddress_Null() throws Exception { + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(null); + + // Assert + assertThat(result).isNull(); + } + + /** + * Test resolvePolarisAddressFromConfigAddress when configAddress is empty string. + * Scenario: pass empty string as configAddress. + * Expect: return null directly without calling AddressUtils. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should return null when configAddress is empty") + @Test + void testResolvePolarisAddressFromConfigAddress_Empty() throws Exception { + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(""); + + // Assert + assertThat(result).isNull(); + } + + /** + * Test resolvePolarisAddressFromConfigAddress with a single address containing port. + * Scenario: pass a single config address "grpc://192.168.1.100:8093". + * Expect: the port should be replaced with 8091. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should replace port with 8091 for single address") + @Test + void testResolvePolarisAddressFromConfigAddress_SingleAddress() throws Exception { + // Arrange + String configAddress = "grpc://192.168.1.100:8093"; + + try (MockedStatic mockedAddressUtils = Mockito.mockStatic(AddressUtils.class)) { + mockedAddressUtils.when(() -> AddressUtils.parseAddressList(anyString())) + .thenReturn(Collections.singletonList("192.168.1.100:8093")); + + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(configAddress); + + // Assert + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo("192.168.1.100:8091"); + } + } + + /** + * Test resolvePolarisAddressFromConfigAddress with multiple addresses containing ports. + * Scenario: pass multiple config addresses separated by comma. + * Expect: each address port should be replaced with 8091. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should replace port with 8091 for multiple addresses") + @Test + void testResolvePolarisAddressFromConfigAddress_MultipleAddresses() throws Exception { + // Arrange + String configAddress = "grpc://10.0.1.1:8093,grpc://10.0.1.2:8094"; + + try (MockedStatic mockedAddressUtils = Mockito.mockStatic(AddressUtils.class)) { + mockedAddressUtils.when(() -> AddressUtils.parseAddressList(anyString())) + .thenReturn(Arrays.asList("10.0.1.1:8093", "10.0.1.2:8094")); + + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(configAddress); + + // Assert + assertThat(result).isNotNull(); + assertThat(result).hasSize(2); + assertThat(result.get(0)).isEqualTo("10.0.1.1:8091"); + assertThat(result.get(1)).isEqualTo("10.0.1.2:8091"); + } + } + + /** + * Test resolvePolarisAddressFromConfigAddress with address that has no port (no colon). + * Scenario: AddressUtils returns an address without colon separator. + * Expect: the address should be kept as-is. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should keep address as-is when no port") + @Test + void testResolvePolarisAddressFromConfigAddress_AddressWithoutPort() throws Exception { + // Arrange + String configAddress = "grpc://localhost"; + + try (MockedStatic mockedAddressUtils = Mockito.mockStatic(AddressUtils.class)) { + mockedAddressUtils.when(() -> AddressUtils.parseAddressList(anyString())) + .thenReturn(Collections.singletonList("localhost")); + + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(configAddress); + + // Assert + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo("localhost"); + } + } + + /** + * Test resolvePolarisAddressFromConfigAddress with blank address in the list. + * Scenario: AddressUtils returns a list containing blank strings. + * Expect: blank addresses should be skipped. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should skip blank addresses") + @Test + void testResolvePolarisAddressFromConfigAddress_BlankAddressSkipped() throws Exception { + // Arrange + String configAddress = "grpc://172.16.0.1:8093, ,grpc://172.16.0.2:9090"; + + try (MockedStatic mockedAddressUtils = Mockito.mockStatic(AddressUtils.class)) { + mockedAddressUtils.when(() -> AddressUtils.parseAddressList(anyString())) + .thenReturn(Arrays.asList("172.16.0.1:8093", " ", "172.16.0.2:9090")); + + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(configAddress); + + // Assert + assertThat(result).isNotNull(); + assertThat(result).hasSize(2); + assertThat(result.get(0)).isEqualTo("172.16.0.1:8091"); + assertThat(result.get(1)).isEqualTo("172.16.0.2:8091"); + } + } + + /** + * Test resolvePolarisAddressFromConfigAddress with IPv6 address containing port. + * Scenario: pass an IPv6 config address like "grpc://[::1]:8093". + * Expect: the port should be replaced with 8091, preserving the IPv6 format. + */ + @DisplayName("resolvePolarisAddressFromConfigAddress should replace port with 8091 for IPv6 address") + @Test + void testResolvePolarisAddressFromConfigAddress_Ipv6Address() throws Exception { + // Arrange + String configAddress = "grpc://[::1]:8093"; + + try (MockedStatic mockedAddressUtils = Mockito.mockStatic(AddressUtils.class)) { + mockedAddressUtils.when(() -> AddressUtils.parseAddressList(anyString())) + .thenReturn(Collections.singletonList("[::1]:8093")); + + // Act + List result = invokeResolvePolarisAddressFromConfigAddress(configAddress); + + // Assert + assertThat(result).isNotNull(); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo("[::1]:8091"); + } + } +}