From efb6f4a0578446f4c07c38d0551458fbc5871c4f Mon Sep 17 00:00:00 2001 From: Haotian Zhang <928016560@qq.com> Date: Fri, 9 May 2025 18:01:34 +0800 Subject: [PATCH] feat:support smooth upgrade from tsf. --- .../pom.xml | 1 - .../common/PolarisResultToErrorCodeTest.java | 95 ++++++++ ...gnCircuitBreakerInvocationHandlerTest.java | 216 ++++++++++++++++++ .../feign/PolarisFeignCircuitBreakerTest.java | 133 +++++++++++ ...arisRefreshEntireContextRefresherTest.java | 209 +++++++++++++++++ .../contract/PolarisContractReporter.java | 3 +- .../contract/filter/FilterConstant.java | 2 +- .../pom.xml | 6 + .../filter/QuotaCheckReactiveFilter.java | 1 + .../filter/QuotaCheckServletFilter.java | 1 + .../config/RouterAutoConfiguration.java | 2 +- .../zuul/PolarisRibbonRoutingFilter.java | 2 +- .../zuul/PolarisRibbonRoutingFilterTest.java | 2 +- .../metadata/MetadataContextHolder.java | 13 +- spring-cloud-tencent-dependencies/pom.xml | 10 +- spring-cloud-tencent-plugin-starters/pom.xml | 1 + .../pom.xml | 59 +++++ .../cloud/plugin/protection/ExitUtils.java | 52 +++++ .../SecurityProtectionAutoConfiguration.java | 87 +++++++ .../main/resources/META-INF/spring.factories | 3 + ...curityProtectionAutoConfigurationTest.java | 189 +++++++++++++++ spring-cloud-tencent-polaris-context/pom.xml | 10 + .../tsf/TsfCoreEnvironmentPostProcessor.java | 4 + .../FailedEventApplicationListener.java | 69 ++++++ .../main/resources/META-INF/spring.factories | 3 +- .../FailedEventApplicationListenerTest.java | 154 +++++++++++++ spring-cloud-tencent-rpc-enhancement/pom.xml | 6 + .../RpcEnhancementAutoConfiguration.java | 6 - .../scg/EnhancedGatewayGlobalFilter.java | 3 +- ...hancedWebClientExchangeFilterFunction.java | 41 +++- .../zuul/EnhancedErrorZuulFilter.java | 2 +- .../zuul/EnhancedPostZuulFilter.java | 2 +- .../zuul/EnhancedRouteZuulFilter.java | 2 +- .../zuul/EnhancedZuulPluginRunner.java | 2 +- .../plugin/DefaultEnhancedPluginRunner.java | 26 +-- .../transformer/InstanceTransformer.java | 11 +- .../DefaultEnhancedPluginRunnerTest.java | 90 ++++++++ .../plugin/EnhancedPluginContextTest.java | 1 + 38 files changed, 1478 insertions(+), 41 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresherTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/ExitUtils.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/test/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListener.java create mode 100644 spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListenerTest.java rename spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/{ => instrument}/zuul/EnhancedErrorZuulFilter.java (98%) rename spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/{ => instrument}/zuul/EnhancedPostZuulFilter.java (98%) rename spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/{ => instrument}/zuul/EnhancedRouteZuulFilter.java (98%) rename spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/{ => instrument}/zuul/EnhancedZuulPluginRunner.java (98%) create mode 100644 spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java diff --git a/spring-cloud-starter-tencent-metadata-transfer/pom.xml b/spring-cloud-starter-tencent-metadata-transfer/pom.xml index 27ce74c9d..34658ad47 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/pom.xml +++ b/spring-cloud-starter-tencent-metadata-transfer/pom.xml @@ -15,7 +15,6 @@ - com.tencent.cloud spring-cloud-tencent-rpc-enhancement diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java new file mode 100644 index 000000000..6555e4882 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/common/PolarisResultToErrorCodeTest.java @@ -0,0 +1,95 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.circuitbreaker.common; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for ${@link PolarisResultToErrorCode}. + * + * @author Shedfree Wu + */ +@ExtendWith(MockitoExtension.class) +class PolarisResultToErrorCodeTest { + + private final PolarisResultToErrorCode converter = new PolarisResultToErrorCode(); + + @Test + void testOnSuccess() { + assertThat(converter.onSuccess("any value")).isEqualTo(200); + } + + @Test + void testOnErrorWithWebClientResponseException() { + // Given + WebClientResponseException exception = WebClientResponseException.create( + 404, "Not Found", null, null, null); + + // When + int errorCode = converter.onError(exception); + + // Then + assertThat(errorCode).isEqualTo(404); + } + + @Test + void testOnErrorWithCircuitBreakerStatusCodeException() { + // When + int errorCode = converter.onError(new RuntimeException("test")); + + // Then + assertThat(errorCode).isEqualTo(-1); + } + + @Test + void testOnErrorWithUnknownException() { + // Given + RuntimeException exception = new RuntimeException("Unknown error"); + + // When + int errorCode = converter.onError(exception); + + // Then + assertThat(errorCode).isEqualTo(-1); + } + + @Test + void testCheckClassExist() throws Exception { + // Given + Method checkClassExist = PolarisResultToErrorCode.class.getDeclaredMethod("checkClassExist", String.class); + checkClassExist.setAccessible(true); + + PolarisResultToErrorCode converter = new PolarisResultToErrorCode(); + + // test exist class + boolean result1 = (boolean) checkClassExist.invoke(converter, "java.lang.String"); + assertThat(result1).isTrue(); + + // test not exist class + boolean result2 = (boolean) checkClassExist.invoke(converter, "com.nonexistent.Class"); + assertThat(result2).isFalse(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java new file mode 100644 index 000000000..2b142e802 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerInvocationHandlerTest.java @@ -0,0 +1,216 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.circuitbreaker.instrument.feign; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException; +import com.tencent.polaris.api.pojo.CircuitBreakerStatus; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.codec.Decoder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.openfeign.FallbackFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for ${@link PolarisFeignCircuitBreakerInvocationHandler}. + * + * @author Shedfree Wu + */ +@ExtendWith(MockitoExtension.class) +class PolarisFeignCircuitBreakerInvocationHandlerTest { + + @Mock + private Target target; + + @Mock + private InvocationHandlerFactory.MethodHandler methodHandler; + + @Mock + private FallbackFactory fallbackFactory; + + @Mock + private Decoder decoder; + + private Map dispatch; + private PolarisFeignCircuitBreakerInvocationHandler handler; + + private Method testMethod; + + @BeforeEach + void setUp() throws Exception { + dispatch = new HashMap<>(); + + testMethod = TestInterface.class.getDeclaredMethod("testMethod"); + dispatch.put(testMethod, methodHandler); + + handler = new PolarisFeignCircuitBreakerInvocationHandler( + target, + dispatch, + fallbackFactory, + decoder + ); + } + + @Test + void testConstructorWithNullTarget() { + assertThatThrownBy(() -> + new PolarisFeignCircuitBreakerInvocationHandler( + null, dispatch, fallbackFactory, decoder + ) + ).isInstanceOf(NullPointerException.class); + + } + + @Test + void testConstructorWithNullDispatch() { + assertThatThrownBy(() -> + new PolarisFeignCircuitBreakerInvocationHandler( + target, null, fallbackFactory, decoder + )).isInstanceOf(NullPointerException.class); + } + + @Test + void testToFallbackMethod() throws Exception { + Method method = TestInterface.class.getMethod("testMethod"); + Map testDispatch = new HashMap<>(); + testDispatch.put(method, methodHandler); + + Map result = PolarisFeignCircuitBreakerInvocationHandler.toFallbackMethod(testDispatch); + + assertThat(result).isNotNull(); + assertThat(result.containsKey(method)).isTrue(); + assertThat(result.get(method)).isEqualTo(method); + } + + @Test + void testEqualsMethod() throws Throwable { + Method equalsMethod = Object.class.getMethod("equals", Object.class); + Object mockProxy = mock(Object.class); + + // Test equals with null + assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {null})).isFalse(); + + // Test equals with non-proxy object + assertThat((Boolean) handler.invoke(mockProxy, equalsMethod, new Object[] {new Object()})).isFalse(); + } + + @Test + void testToStringMethod() throws Throwable { + Method toStringMethod = Object.class.getMethod("toString"); + Object mockProxy = mock(Object.class); + when(target.toString()).thenReturn("TestTarget"); + + assertThat(handler.invoke(mockProxy, toStringMethod, null)).isEqualTo("TestTarget"); + } + + @Test + void testCustomFallbackFactoryWithFallbackError() throws Throwable { + // Arrange + handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, fallbackFactory, decoder); + Exception originalException = new RuntimeException("Original error"); + + when(methodHandler.invoke(any())).thenThrow(originalException); + TestImpl testImpl = new TestImpl(); + when(fallbackFactory.create(any())).thenReturn(testImpl); + + // Act + assertThatThrownBy(() -> handler.invoke(null, testMethod, new Object[] {})).isInstanceOf(FallbackWrapperException.class); + } + + @Test + void testCustomFallbackFactoryWithFallbackError2() throws Throwable { + // Arrange + handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, fallbackFactory, decoder); + Exception originalException = new RuntimeException("Original error"); + + when(methodHandler.invoke(any())).thenThrow(originalException); + TestImpl2 testImpl = new TestImpl2(); + when(fallbackFactory.create(any())).thenReturn(testImpl); + + // Act + assertThatThrownBy(() -> handler.invoke(null, testMethod, new Object[] {})). + isInstanceOf(RuntimeException.class).hasMessage("test"); + } + + @Test + void testDefaultFallbackCreation() throws Throwable { + // Arrange + handler = new PolarisFeignCircuitBreakerInvocationHandler(target, dispatch, null, decoder); + CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, new HashMap<>(), "mock body"); + CallAbortedException originalException = new CallAbortedException("test rule", fallbackInfo); + + Object expected = new Object(); + when(methodHandler.invoke(any())).thenThrow(originalException); + when(decoder.decode(any(), any())).thenReturn(expected); + + // Act + Object result = handler.invoke(null, testMethod, new Object[] {}); + + // Verify + assertThat(result).isEqualTo(expected); + } + + @Test + void testEquals() { + PolarisFeignCircuitBreakerInvocationHandler testHandler = new PolarisFeignCircuitBreakerInvocationHandler( + target, + dispatch, + fallbackFactory, + decoder + ); + + assertThat(testHandler).isEqualTo(handler); + assertThat(testHandler.hashCode()).isEqualTo(handler.hashCode()); + } + + interface TestInterface { + String testMethod() throws InvocationTargetException; + } + + static class TestImpl implements TestInterface { + + @Override + public String testMethod() throws InvocationTargetException { + throw new InvocationTargetException(new RuntimeException("test")); + } + } + + static class TestImpl2 implements TestInterface { + + @Override + public String testMethod() throws InvocationTargetException { + throw new RuntimeException("test"); + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java new file mode 100644 index 000000000..2805d9264 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/feign/PolarisFeignCircuitBreakerTest.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.circuitbreaker.instrument.feign; + +import feign.Feign; +import feign.RequestLine; +import feign.Target; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.openfeign.FallbackFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +/** + * Tests for {@link PolarisFeignCircuitBreaker}. + */ +public class PolarisFeignCircuitBreakerTest { + + private PolarisFeignCircuitBreaker.Builder builder; + + @BeforeEach + public void setUp() { + builder = PolarisFeignCircuitBreaker.builder(); + } + + @Test + public void testBuilderNotNull() { + assertThat(builder).isNotNull(); + } + + @Test + public void testTargetWithFallback() { + // Mock the target + Class targetType = MyService.class; + String name = "myService"; + Target target = mock(Target.class); + + // mock return value + when(target.type()).thenReturn(targetType); + when(target.name()).thenReturn(name); + + // Mock the fallback + MyService fallback = mock(MyService.class); + when(fallback.sayHello()).thenReturn("Fallback Hello"); + + // Call the target method + MyService result = builder.target(target, fallback); + + // Verify that the result is not null and the fallback factory is used + assertThat(result).isNotNull(); + assertThat(result.sayHello()).isEqualTo("Fallback Hello"); + } + + @Test + public void testTargetWithFallbackFactory() { + // Mock the target and fallback factory + Class targetType = MyService.class; + String name = "myService"; + Target target = mock(Target.class); + + // mock return value + when(target.type()).thenReturn(targetType); + when(target.name()).thenReturn(name); + + FallbackFactory fallbackFactory = mock(FallbackFactory.class); + + // Mock the fallback from the factory + MyService fallback = mock(MyService.class); + when(fallback.sayHello()).thenReturn("Fallback Hello"); + when(fallbackFactory.create(any())).thenReturn(fallback); + + // Call the target method + MyService result = builder.target(target, fallbackFactory); + + // Verify that the result is not null and the fallback factory is used + assertThat(result).isNotNull(); + assertThat(result.sayHello()).isEqualTo("Fallback Hello"); + } + + @Test + public void testTargetWithoutFallback() { + // Mock the target + Class targetType = MyService.class; + String name = "myService"; + Target target = mock(Target.class); + + // mock return value + when(target.type()).thenReturn(targetType); + when(target.name()).thenReturn(name); + + // Call the target method + MyService result = builder.target(target); + + // Verify that the result is not null + assertThat(result).isNotNull(); + // Additional verifications can be added here based on the implementation + } + + @Test + public void testBuildWithNullableFallbackFactory() { + // Call the build method with a null fallback factory + Feign feign = builder.build(null); + + // Verify that the Feign instance is not null + assertThat(feign).isNotNull(); + // Additional verifications can be added here based on the implementation + } + + public interface MyService { + @RequestLine("GET /hello") + String sayHello(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresherTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresherTest.java new file mode 100644 index 000000000..dc90f2e37 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresherTest.java @@ -0,0 +1,209 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.adapter; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link PolarisRefreshEntireContextRefresher}. + * + * @author Shedfree Wu + */ +public class PolarisRefreshEntireContextRefresherTest { + @Mock + private PolarisConfigProperties polarisConfigProperties; + + @Mock + private SpringValueRegistry springValueRegistry; + + @Mock + private ConfigFileService configFileService; + + @Mock + private ContextRefresher contextRefresher; + + @Mock + private ConfigurableApplicationContext applicationContext; + + @Mock + private PolarisConfigCustomExtensionLayer mockExtensionLayer; + + private PolarisRefreshEntireContextRefresher refresher; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + refresher = new PolarisRefreshEntireContextRefresher( + polarisConfigProperties, + springValueRegistry, + configFileService, + contextRefresher + ); + refresher.setApplicationContext(applicationContext); + } + + @Test + void testRefreshSpringValue() { + // test refreshSpringValue method, it should do nothing + refresher.refreshSpringValue("test.key"); + + // Verify + verifyNoInteractions(contextRefresher); + verifyNoInteractions(springValueRegistry); + } + + @Test + void testRefreshConfigurationPropertiesWithRefreshScope() { + // Arrange + Set changeKeys = new HashSet<>(); + changeKeys.add("test.key1"); + changeKeys.add("test.key2"); + + // mock test.key1 in refresh scope + when(springValueRegistry.isRefreshScopeKey("test.key1")).thenReturn(true); + + // Act + refresher.refreshConfigurationProperties(changeKeys); + + // Verify + verify(contextRefresher, times(1)).refresh(); + verifyNoInteractions(applicationContext); + } + + @Test + void testRefreshConfigurationPropertiesWithoutRefreshScope() { + // Arrange + Set changeKeys = new HashSet<>(); + changeKeys.add("test.key1"); + changeKeys.add("test.key2"); + + // mock a key not in refresh scope + when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(false); + + // Act + refresher.refreshConfigurationProperties(changeKeys); + + // Verify + verify(contextRefresher, never()).refresh(); + verify(applicationContext, times(1)) + .publishEvent(any(EnvironmentChangeEvent.class)); + } + + @Test + void testSetApplicationContext() { + // Arrange + ConfigurableApplicationContext newContext = mock(ConfigurableApplicationContext.class); + + // Act + refresher.setApplicationContext(newContext); + + // Verify + Set changeKeys = new HashSet<>(); + changeKeys.add("test.key"); + when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(false); + + refresher.refreshConfigurationProperties(changeKeys); + verify(newContext, times(1)).publishEvent(any(EnvironmentChangeEvent.class)); + } + + @Test + void testRefreshConfigurationPropertiesWithEmptyChangeKeys() { + // Arrange + Set changeKeys = new HashSet<>(); + + // Act + refresher.refreshConfigurationProperties(changeKeys); + + // Verify + verify(contextRefresher, never()).refresh(); + verify(applicationContext, times(1)) + .publishEvent(any(EnvironmentChangeEvent.class)); + } + + @Test + void testRefreshConfigurationPropertiesWithMultipleRefreshScopeKeys() { + // Arrange + Set changeKeys = new HashSet<>(); + changeKeys.add("test.key1"); + changeKeys.add("test.key2"); + changeKeys.add("test.key3"); + + // mock multiple keys in refresh scope + when(springValueRegistry.isRefreshScopeKey(anyString())).thenReturn(true); + + // Act + refresher.refreshConfigurationProperties(changeKeys); + + // Verify + verify(contextRefresher, times(1)).refresh(); + verifyNoInteractions(applicationContext); + } + + @Test + void testPolarisConfigCustomExtensionLayer() throws Exception { + refresher.setRegistered(true); + + Field field = PolarisConfigPropertyAutoRefresher.class + .getDeclaredField("polarisConfigCustomExtensionLayer"); + field.setAccessible(true); + field.set(refresher, mockExtensionLayer); + + Method method = PolarisConfigPropertyAutoRefresher.class + .getDeclaredMethod("customInitRegisterPolarisConfig", PolarisConfigPropertyAutoRefresher.class); + method.setAccessible(true); + method.invoke(refresher, refresher); + + + method = PolarisConfigPropertyAutoRefresher.class.getDeclaredMethod( + "customRegisterPolarisConfigPublishChangeListener", + PolarisPropertySource.class, PolarisPropertySource.class); + + method.setAccessible(true); + method.invoke(refresher, null, null); + + // Verify + verify(mockExtensionLayer, times(1)).initRegisterConfig(refresher); + + } + +} diff --git a/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/PolarisContractReporter.java b/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/PolarisContractReporter.java index f6682b2f7..66e2e01bb 100644 --- a/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/PolarisContractReporter.java +++ b/spring-cloud-starter-tencent-polaris-contract/src/main/java/com/tencent/cloud/polaris/contract/PolarisContractReporter.java @@ -25,6 +25,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.GzipUtil; import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.contract.config.PolarisContractProperties; @@ -100,7 +101,7 @@ public class PolarisContractReporter implements ApplicationListener com.tencent.cloud spring-cloud-tencent-rpc-enhancement + + + spring-security-crypto + org.springframework.security + + com.tencent.cloud diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index 02a77ed76..07a13e6e2 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -104,6 +104,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, path); if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { + LOG.info("block by ratelimit rule, uri:{}", exchange.getRequest().getURI()); ServerHttpResponse response = exchange.getResponse(); DataBuffer dataBuffer; if (Objects.nonNull(quotaResponse.getActiveRule()) diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index 0828efeec..a93d26686 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -98,6 +98,7 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { try { quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, request.getRequestURI()); if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { + LOG.info("block by ratelimit rule, uri:{}", request.getRequestURI()); if (Objects.nonNull(quotaResponse.getActiveRule()) && StringUtils.isNotBlank(quotaResponse.getActiveRule().getCustomResponse().getBody())) { response.setStatus(polarisRateLimitProperties.getRejectHttpCode()); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index a2f0e460a..588a8b642 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -43,8 +43,8 @@ import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; import com.tencent.cloud.polaris.router.zuul.PolarisRibbonRoutingFilter; import com.tencent.cloud.polaris.router.zuul.RouterLabelZuulFilter; +import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; -import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java index c8a788e83..ee59ac981 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilter.java @@ -38,7 +38,7 @@ import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; -import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner; +import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner; import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java index b6a6fda1c..0cbd74a45 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/zuul/PolarisRibbonRoutingFilterTest.java @@ -37,7 +37,7 @@ import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.ServletRouterLabelResolver; -import com.tencent.cloud.rpc.enhancement.zuul.EnhancedZuulPluginRunner; +import com.tencent.cloud.rpc.enhancement.instrument.zuul.EnhancedZuulPluginRunner; import okhttp3.OkHttpClient; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java index d3a27fcea..55473bfd8 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/MetadataContextHolder.java @@ -158,24 +158,25 @@ public final class MetadataContextHolder { Map dynamicApplicationMetadata, MetadataProvider callerMetadataProvider) { com.tencent.polaris.metadata.core.manager.MetadataContextHolder.refresh(metadataManager -> { // caller transitive metadata to local custom transitive metadata - MetadataContainer metadataContainerUpstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false); + MetadataContainer calleeCustomMetadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false); if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) { for (Map.Entry entry : dynamicTransitiveMetadata.entrySet()) { - metadataContainerUpstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH); + calleeCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.PASS_THROUGH); } } // caller disposable metadata to caller custom disposable metadata - MetadataContainer metadataContainerDownstream = metadataManager.getMetadataContainer(MetadataType.CUSTOM, false); + MetadataContainer callerCustomMetadataContainer = metadataManager.getMetadataContainer(MetadataType.CUSTOM, true); if (!CollectionUtils.isEmpty(dynamicDisposableMetadata)) { for (Map.Entry entry : dynamicDisposableMetadata.entrySet()) { - metadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.NONE); + calleeCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.NONE); + callerCustomMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE); } } // caller application metadata to caller application disposable metadata - MetadataContainer applicationMetadataContainerDownstream = metadataManager.getMetadataContainer(MetadataType.APPLICATION, true); + MetadataContainer callerApplicationMetadataContainer = metadataManager.getMetadataContainer(MetadataType.APPLICATION, true); if (!CollectionUtils.isEmpty(dynamicApplicationMetadata)) { for (Map.Entry entry : dynamicApplicationMetadata.entrySet()) { - applicationMetadataContainerDownstream.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE); + callerApplicationMetadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), TransitiveType.DISPOSABLE); } } diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 7637a2a43..751023f9e 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -73,13 +73,13 @@ 2.0.2.0-Hoxton.SR12-SNAPSHOT - 2.0.0.0 + 2.0.0.1 32.1.3-jre 1.2.13 1.7.0 1.5.24 4.5.1 - 1.12.10 + 1.12.19 2.15.3 3.21.7 2.9.9 @@ -238,6 +238,12 @@ ${revision} + + com.tencent.cloud + spring-cloud-tencent-security-protection-plugin + ${revision} + + com.google.guava diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index b80d06b7f..4159c7843 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -22,6 +22,7 @@ spring-cloud-starter-tencent-threadlocal-plugin spring-cloud-starter-tencent-trace-plugin spring-cloud-starter-tencent-fault-tolerance + spring-cloud-tencent-security-protection-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/pom.xml new file mode 100644 index 000000000..1674db4e8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/pom.xml @@ -0,0 +1,59 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-security-protection-plugin + Spring Cloud Tencent Lossless Plugin + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework + spring-beans + + + + org.slf4j + slf4j-api + + + + org.springframework + spring-webmvc + true + + + + org.springframework + spring-webflux + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.mockito + mockito-inline + test + + + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/ExitUtils.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/ExitUtils.java new file mode 100644 index 000000000..2bf12db99 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/ExitUtils.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.plugin.protection; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * ExitUtils. + * + * @author Shedfree Wu + */ +public final class ExitUtils { + + private ExitUtils() { + + } + + public static void exit(ApplicationContext context) { + exit(context, 3000); + } + + public static void exit(ApplicationContext context, int delay) { + if (context instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) context; + configurableContext.close(); + } + + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + // do nothing + } + System.exit(0); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfiguration.java new file mode 100644 index 000000000..589240c63 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.plugin.protection; + + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.function.RouterFunction; + +/** + * SecurityProtectionAutoConfiguration. + * + * @author Shedfree Wu + */ +@Configuration +public class SecurityProtectionAutoConfiguration { + + private static final Logger LOGGER = LoggerFactory.getLogger(SecurityProtectionAutoConfiguration.class); + + @Configuration + @ConditionalOnProperty(name = "spring.cloud.tencent.security.protection.servlet.enabled", matchIfMissing = true) + @ConditionalOnClass(name = {"org.springframework.web.servlet.function.RouterFunction"}) + static class ServletProtectionConfiguration implements InitializingBean { + + @Autowired(required = false) + List routerFunctions; + + @Autowired + ApplicationContext applicationContext; + + @Override + public void afterPropertiesSet() { + if (routerFunctions != null && !routerFunctions.isEmpty()) { + LOGGER.error("Detected the presence of webmvc RouterFunction-related beans, which may trigger the CVE-2024-38819 vulnerability. The program will soon exit."); + LOGGER.error("routerFunctions:{}: ", routerFunctions); + + ExitUtils.exit(applicationContext); + } + } + } + + @Configuration + @ConditionalOnProperty(name = "spring.cloud.tencent.security.protection.reactive.enabled", matchIfMissing = true) + @ConditionalOnClass(name = {"org.springframework.web.reactive.function.server.RouterFunction"}) + static class ReactiveProtectionConfiguration implements InitializingBean { + + @Autowired(required = false) + List routerFunctions; + + @Autowired + ApplicationContext applicationContext; + + @Override + public void afterPropertiesSet() { + if (routerFunctions != null && !routerFunctions.isEmpty()) { + LOGGER.error("Detected the presence of webflux RouterFunction-related beans, which may trigger the CVE-2024-38819 vulnerability. The program will soon exit."); + LOGGER.error("routerFunctions:{}: ", routerFunctions); + ExitUtils.exit(applicationContext); + } + } + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..3e0f0ee08 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.tencent.cloud.plugin.protection.SecurityProtectionAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/test/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfigurationTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/test/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfigurationTest.java new file mode 100644 index 000000000..cb93c89ea --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-security-protection-plugin/src/test/java/com/tencent/cloud/plugin/protection/SecurityProtectionAutoConfigurationTest.java @@ -0,0 +1,189 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.plugin.protection; + +import java.security.Permission; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.servlet.function.RouterFunction; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link SecurityProtectionAutoConfiguration}. + */ +@ExtendWith(MockitoExtension.class) +class SecurityProtectionAutoConfigurationTest { + + @Mock + private ConfigurableApplicationContext applicationContext; + + @Test + void testServletProtectionNoRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration(); + config.applicationContext = applicationContext; + config.routerFunctions = null; + + // Act + config.afterPropertiesSet(); + + // Verify + // Should not call exit when no RouterFunctions present + verify(applicationContext, never()).close(); + } + + @Test + void testServletProtectionEmptyRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration(); + config.applicationContext = applicationContext; + config.routerFunctions = new ArrayList<>(); + + // Act + config.afterPropertiesSet(); + + // Verify + // Should not call exit when RouterFunctions list is empty + verify(applicationContext, never()).close(); + } + + @Test + void testServletProtectionWithRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ServletProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ServletProtectionConfiguration(); + config.applicationContext = mock(ConfigurableApplicationContext.class); + List routerFunctions = new ArrayList<>(); + routerFunctions.add(mock(RouterFunction.class)); + config.routerFunctions = routerFunctions; + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + config.afterPropertiesSet(); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + @Test + void testReactiveProtectionNoRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration(); + config.applicationContext = applicationContext; + config.routerFunctions = null; + + // Act + config.afterPropertiesSet(); + + // Verify + verify(applicationContext, never()).close(); + } + + @Test + void testReactiveProtectionEmptyRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration(); + config.applicationContext = applicationContext; + config.routerFunctions = new ArrayList<>(); + + // Act + config.afterPropertiesSet(); + + // Verify + verify(applicationContext, never()).close(); + } + + @Test + void testReactiveProtectionWithRouterFunctions() { + // Arrange + SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration config = + new SecurityProtectionAutoConfiguration.ReactiveProtectionConfiguration(); + config.applicationContext = mock(ConfigurableApplicationContext.class); + List routerFunctions = new ArrayList<>(); + routerFunctions.add(mock(org.springframework.web.reactive.function.server.RouterFunction.class)); + config.routerFunctions = routerFunctions; + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + config.afterPropertiesSet(); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + @Test + void testInterruptedExceptionHandling() throws InterruptedException { + // Arrange + ConfigurableApplicationContext mockContext = mock(ConfigurableApplicationContext.class); + Thread testThread = new Thread(() -> ExitUtils.exit(mockContext, 3000)); + + + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + // Act + testThread.start(); + testThread.interrupt(); + Thread.sleep(6000); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + public static class ExitSecurityManager extends SecurityManager { + + @Override + public void checkPermission(Permission perm) { + if (perm.getName().contains("exitVM")) { + throw new SecurityException("System.exit is not allowed"); + } + } + } +} diff --git a/spring-cloud-tencent-polaris-context/pom.xml b/spring-cloud-tencent-polaris-context/pom.xml index 185c74bd1..375fe7799 100644 --- a/spring-cloud-tencent-polaris-context/pom.xml +++ b/spring-cloud-tencent-polaris-context/pom.xml @@ -179,6 +179,16 @@ com.tencent.polaris registry-memory + + + error_prone_annotations + com.google.errorprone + + + j2objc-annotations + com.google.j2objc + + diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java index 35dac9350..73c0317ef 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreEnvironmentPostProcessor.java @@ -126,6 +126,10 @@ public final class TsfCoreEnvironmentPostProcessor implements EnvironmentPostPro defaultProperties.put("spring.cloud.polaris.discovery.zero-protection.enabled", "true"); defaultProperties.put("spring.cloud.polaris.discovery.zero-protection.is-need-test-connectivity", "true"); defaultProperties.put("spring.cloud.discovery.client.health-indicator.enabled", "false"); + String warmupEnabled = environment.getProperty("spring.cloud.polaris.warmup.enabled"); + if (StringUtils.isBlank(warmupEnabled)) { + defaultProperties.put("spring.cloud.polaris.warmup.enabled", true); + } // contract defaultProperties.put("spring.cloud.polaris.contract.enabled", environment.getProperty("tsf.swagger.enabled", "true")); diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListener.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListener.java new file mode 100644 index 000000000..b963141e3 --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListener.java @@ -0,0 +1,69 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.context.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +/** + * Failed event listener. + * + * @author skyehtzhang + */ +@Configuration +public class FailedEventApplicationListener implements ApplicationListener, ApplicationContextAware { + + private static final Logger logger = LoggerFactory.getLogger(FailedEventApplicationListener.class); + + private ApplicationContext applicationContext; + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ApplicationFailedEvent) { + ApplicationFailedEvent failedEvent = (ApplicationFailedEvent) event; + if (failedEvent.getException() != null) { + logger.error("[onApplicationEvent] exception in failed event", failedEvent.getException()); + } + + if (applicationContext instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) applicationContext).close(); + } + try { + Thread.sleep(3000); + } + catch (InterruptedException e) { + // do nothing + } + System.exit(0); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} diff --git a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories index ac093c297..7751f48cc 100644 --- a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories @@ -6,7 +6,8 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.tencent.cloud.polaris.context.config.PolarisContextBootstrapAutoConfiguration,\ com.tencent.cloud.polaris.context.config.extend.tsf.TsfContextBootstrapConfiguration org.springframework.context.ApplicationListener=\ - com.tencent.cloud.polaris.context.logging.PolarisLoggingApplicationListener + com.tencent.cloud.polaris.context.logging.PolarisLoggingApplicationListener,\ + com.tencent.cloud.polaris.context.listener.FailedEventApplicationListener org.springframework.boot.env.EnvironmentPostProcessor=\ com.tencent.cloud.polaris.context.config.PolarisContextEnvironmentPostProcessor,\ com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreEnvironmentPostProcessor diff --git a/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListenerTest.java b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListenerTest.java new file mode 100644 index 000000000..8e38f769f --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/listener/FailedEventApplicationListenerTest.java @@ -0,0 +1,154 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.context.listener; + +import java.security.Permission; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.mockito.Mockito.when; + + +class FailedEventApplicationListenerTest { + + @Mock + private ConfigurableApplicationContext mockConfigurableContext; + + @Mock + private ApplicationContext mockApplicationContext; + + @Mock + private ApplicationFailedEvent mockFailedEvent; + + private FailedEventApplicationListener listener; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + listener = new FailedEventApplicationListener(); + } + + @Test + void testSetApplicationContext() { + // Test setting application context + listener.setApplicationContext(mockApplicationContext); + } + + @Test + void testOnApplicationEventWithConfigurableContext() { + // Arrange + listener.setApplicationContext(mockConfigurableContext); + when(mockFailedEvent.getException()).thenReturn(new RuntimeException("Test Exception")); + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + // Act + listener.onApplicationEvent(mockFailedEvent); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + @Test + void testOnApplicationEventWithNonConfigurableContext() { + // Arrange + listener.setApplicationContext(mockApplicationContext); + when(mockFailedEvent.getException()).thenReturn(new RuntimeException("Test Exception")); + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + // Act + listener.onApplicationEvent(mockFailedEvent); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + @Test + void testOnApplicationEventWithInterruptedException() { + // Arrange + listener.setApplicationContext(mockConfigurableContext); + Thread.currentThread().interrupt(); // Simulate interruption + + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + // Act + listener.onApplicationEvent(mockFailedEvent); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + @Test + void testOnApplicationEventWithNullException() { + // Arrange + listener.setApplicationContext(mockConfigurableContext); + when(mockFailedEvent.getException()).thenReturn(null); + + + SecurityManager originalSecurityManager = System.getSecurityManager(); + System.setSecurityManager(new ExitSecurityManager()); + + try { + // Act + listener.onApplicationEvent(mockFailedEvent); + } + catch (SecurityException e) { + // Ignore + } + finally { + System.setSecurityManager(originalSecurityManager); + } + } + + public static class ExitSecurityManager extends SecurityManager { + + @Override + public void checkPermission(Permission perm) { + if (perm.getName().contains("exitVM")) { + throw new SecurityException("System.exit is not allowed"); + } + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index bad30193e..6f15acdf3 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -98,6 +98,12 @@ test + + io.projectreactor + reactor-test + test + + org.mockito mockito-inline diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index 606415442..863ced90b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -56,7 +56,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -180,11 +179,6 @@ public class RpcEnhancementAutoConfiguration { @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") protected static class PolarisRestTemplateAutoConfiguration { - @LoadBalanced - @Autowired(required = false) - private List restTemplates = Collections.emptyList(); - - @Bean @ConditionalOnMissingBean @ConditionalOnClass(name = {"org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer"}) diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java index 8d00dce85..4fce65273 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/scg/EnhancedGatewayGlobalFilter.java @@ -145,7 +145,8 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered { Route routeAttr = originExchange.getAttribute(GATEWAY_ROUTE_ATTR); try { return new URI(originExchange.getRequest().getURI().getScheme(), - routeAttr.getUri().getHost(), originExchange.getRequest().getURI().getPath(), originExchange.getRequest().getURI().getRawQuery()); + routeAttr.getUri().getHost(), originExchange.getRequest().getURI() + .getPath(), originExchange.getRequest().getURI().getRawQuery()); } catch (URISyntaxException e) { return null; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java index e8d8f1231..73008f51b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/webclient/EnhancedWebClientExchangeFilterFunction.java @@ -17,15 +17,22 @@ package com.tencent.cloud.rpc.enhancement.instrument.webclient; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; + import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedRequestContext; import com.tencent.cloud.rpc.enhancement.plugin.EnhancedResponseContext; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException; import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; @@ -53,6 +60,7 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu .httpHeaders(originRequest.headers()) .httpMethod(originRequest.method()) .url(originRequest.url()) + .serviceUrl(getServiceUri(originRequest)) .build(); enhancedPluginContext.setRequest(enhancedRequestContext); enhancedPluginContext.setOriginRequest(originRequest); @@ -62,7 +70,21 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), originRequest.url()); // Run post enhanced plugins. - pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); + try { + pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext); + } + catch (CallAbortedException e) { + if (e.getFallbackInfo() == null) { + throw e; + } + HttpStatus httpStatus = HttpStatus.resolve(e.getFallbackInfo().getCode()); + ClientResponse.Builder responseBuilder = ClientResponse.create(httpStatus != null ? httpStatus : HttpStatus.INTERNAL_SERVER_ERROR) + .body(Optional.of(e.getFallbackInfo().getBody()).orElse("")); + if (CollectionUtils.isNotEmpty(e.getFallbackInfo().getHeaders())) { + e.getFallbackInfo().getHeaders().forEach(responseBuilder::header); + } + return Mono.just(responseBuilder.build()); + } // request may be changed by plugin ClientRequest request = (ClientRequest) enhancedPluginContext.getOriginRequest(); long startTime = System.currentTimeMillis(); @@ -91,4 +113,21 @@ public class EnhancedWebClientExchangeFilterFunction implements ExchangeFilterFu pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext); }); } + + private URI getServiceUri(ClientRequest clientRequest) { + Object instance = MetadataContextHolder.get() + .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE); + if (!(instance instanceof ServiceInstance)) { + return null; + } + ServiceInstance serviceInstance = (ServiceInstance) instance; + URI uri = clientRequest.url(); + + try { + return new URI(uri.getScheme(), serviceInstance.getServiceId(), uri.getPath(), uri.getRawQuery()); + } + catch (URISyntaxException e) { + return null; + } + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedErrorZuulFilter.java similarity index 98% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedErrorZuulFilter.java index 248449245..0535184d3 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedErrorZuulFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedErrorZuulFilter.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.zuul; +package com.tencent.cloud.rpc.enhancement.instrument.zuul; import java.util.ArrayList; import java.util.Collection; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedPostZuulFilter.java similarity index 98% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedPostZuulFilter.java index e6ca3eaa9..0c53a4971 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedPostZuulFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedPostZuulFilter.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.zuul; +package com.tencent.cloud.rpc.enhancement.instrument.zuul; import java.util.ArrayList; import java.util.Collection; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedRouteZuulFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedRouteZuulFilter.java similarity index 98% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedRouteZuulFilter.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedRouteZuulFilter.java index 6b900416c..e8830ee4e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedRouteZuulFilter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedRouteZuulFilter.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.zuul; +package com.tencent.cloud.rpc.enhancement.instrument.zuul; import java.net.URI; import java.net.URISyntaxException; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedZuulPluginRunner.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedZuulPluginRunner.java similarity index 98% rename from spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedZuulPluginRunner.java rename to spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedZuulPluginRunner.java index 0499c3ac2..75d856137 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/zuul/EnhancedZuulPluginRunner.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/zuul/EnhancedZuulPluginRunner.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.rpc.enhancement.zuul; +package com.tencent.cloud.rpc.enhancement.instrument.zuul; import java.net.URI; import java.net.URISyntaxException; diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java index f45e29689..bbb97b38f 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunner.java @@ -73,6 +73,19 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner { } } + static Registration getPolarisRegistration(List registration) { + + if (CollectionUtils.isEmpty(registration)) { + return null; + } + for (Registration reg : registration) { + if ("com.tencent.cloud.polaris.registry.PolarisRegistration".equals(reg.getClass().getName())) { + return reg; + } + } + return registration.get(0); + } + /** * run the plugin. * @@ -98,17 +111,4 @@ public class DefaultEnhancedPluginRunner implements EnhancedPluginRunner { public ServiceInstance getLocalServiceInstance() { return this.localServiceInstance; } - - private static Registration getPolarisRegistration(List registration) { - - if (CollectionUtils.isEmpty(registration)) { - return null; - } - for (Registration reg : registration) { - if ("com.tencent.cloud.polaris.registry.PolarisRegistration".equals(reg.getClass().getName())) { - return reg; - } - } - return registration.get(0); - } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/InstanceTransformer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/InstanceTransformer.java index 500664d93..77fa49edc 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/InstanceTransformer.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/transformer/InstanceTransformer.java @@ -20,6 +20,7 @@ package com.tencent.cloud.rpc.enhancement.transformer; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.utils.StringUtils; import org.springframework.cloud.client.ServiceInstance; @@ -41,10 +42,18 @@ public interface InstanceTransformer { instance.setNamespace(MetadataContext.LOCAL_NAMESPACE); instance.setService(serviceInstance.getServiceId()); instance.setProtocol(serviceInstance.getScheme()); - instance.setId(serviceInstance.getInstanceId()); instance.setHost(serviceInstance.getHost()); instance.setPort(serviceInstance.getPort()); instance.setMetadata(serviceInstance.getMetadata()); + instance.setWeight(100); + instance.setHealthy(true); + + if (StringUtils.isNotEmpty(serviceInstance.getInstanceId())) { + instance.setId(serviceInstance.getInstanceId()); + } + else { + instance.setId(serviceInstance.getHost() + ":" + serviceInstance.getPort()); + } } void transformCustom(DefaultInstance instance, ServiceInstance serviceInstance); diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java new file mode 100644 index 000000000..2b899a1ee --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/DefaultEnhancedPluginRunnerTest.java @@ -0,0 +1,90 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. 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.rpc.enhancement.plugin; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cloud.client.serviceregistry.Registration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultEnhancedPluginRunner}. + * + * @author Shedfree Wu + */ +@ExtendWith(MockitoExtension.class) +public class DefaultEnhancedPluginRunnerTest { + + @Test + void getPolarisRegistration_WithPolarisRegistration_ReturnsPolarisRegistration() { + // Arrange + List registrations = new ArrayList<>(); + Registration normalReg = createMockRegistration(); + Registration normalReg2 = createMockRegistration(); + registrations.add(normalReg); + registrations.add(normalReg2); + + // Act + Registration result = DefaultEnhancedPluginRunner.getPolarisRegistration(registrations); + + // Assert + assertThat(result).isSameAs(normalReg); + } + + // Helper method to create mock Registration objects + private Registration createMockRegistration() { + return new Registration() { + @Override + public String getServiceId() { + return null; + } + + @Override + public String getHost() { + return null; + } + + @Override + public int getPort() { + return 0; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public java.net.URI getUri() { + return null; + } + + @Override + public java.util.Map getMetadata() { + return null; + } + }; + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java index 28319b884..59b2a0d23 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/plugin/EnhancedPluginContextTest.java @@ -98,6 +98,7 @@ public class EnhancedPluginContextTest { EnhancedRequestContext requestContext = new EnhancedRequestContext(); requestContext.setHttpHeaders(new HttpHeaders()); requestContext.setUrl(new URI("/")); + requestContext.setServiceUrl(new URI("http://test-service/path")); requestContext.setHttpMethod(HttpMethod.GET); EnhancedRequestContext requestContext1 = EnhancedRequestContext.builder()