From 57ccaeee270fb58953d698762e9ce2b32cbc0e7a Mon Sep 17 00:00:00 2001 From: hanbingleixue Date: Mon, 28 Apr 2025 10:56:55 +0800 Subject: [PATCH] Fix some bug of flowcontrol plugin Signed-off-by: hanbingleixue --- .../common/plugin-change-check/action.yml | 2 +- .../flowcontrol/XdsFlowControlTest.java | 10 +- .../AbstractXdsHttpClientInterceptor.java | 7 +- .../DispatcherServletInterceptor.java | 8 +- .../retry/client/OkHttp3ClientDeclarer.java | 4 +- .../OkHttpClientInterceptorChainDeclarer.java | 4 +- .../client/HttpClient4xInterceptorTest.java | 174 +++++++++++++++++ ...tpUrlConnectionConnectInterceptorTest.java | 170 +++++++++++++++++ ...nnectionResponseStreamInterceptorTest.java | 141 ++++++++++++++ .../retry/client/OkHttp3InterceptorTest.java | 175 +++++++++++++++++ ...ClientInterceptorChainInterceptorTest.java | 177 ++++++++++++++++++ .../handler/XdsFaultRequestHandlerTest.java | 100 ++++++++++ .../XdsRateLimitRequestHandlerTest.java | 106 +++++++++++ .../XdsFlowControlServiceImplTest.java | 87 +++++++++ 14 files changed, 1145 insertions(+), 20 deletions(-) create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpClient4xInterceptorTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionConnectInterceptorTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionResponseStreamInterceptorTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttp3InterceptorTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainInterceptorTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsFaultRequestHandlerTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsRateLimitRequestHandlerTest.java create mode 100644 sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/service/XdsFlowControlServiceImplTest.java diff --git a/.github/actions/common/plugin-change-check/action.yml b/.github/actions/common/plugin-change-check/action.yml index 516ebf78f0..a389b29f1c 100644 --- a/.github/actions/common/plugin-change-check/action.yml +++ b/.github/actions/common/plugin-change-check/action.yml @@ -966,6 +966,6 @@ runs: # ==========xds service is needed to test?========== if [ ${{ env.sermantAgentCoreXdsServiceChanged }} == 'true' -o \ ${{ steps.changed-common-action.outputs.changed }} == 'true' -o ${{ env.triggerPushEvent }} == 'true' -o \ - ${{ env.sermantFlowcontrolChanged }} == 'true'];then + ${{ env.sermantFlowcontrolChanged }} == 'true' ];then echo "enableXdsFlowControl=true" >> $GITHUB_ENV fi diff --git a/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java index d255c200d6..8b96c2c64d 100644 --- a/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java +++ b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java @@ -129,15 +129,15 @@ public void testRetry() { // Test meet the matching rules, retry will not be triggered result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_CLIENT, "v1")); - Assertions.assertEquals("3", result.getData()); - result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v1")); - Assertions.assertEquals("3", result.getData()); + Assertions.assertEquals("2", result.getData()); result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v1")); Assertions.assertEquals("4", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v1")); + Assertions.assertEquals("3", result.getData()); result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_URL_CONNECTION, "v1")); - Assertions.assertEquals("4", result.getData()); - result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP3, "v1")); Assertions.assertEquals("5", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP3, "v1")); + Assertions.assertTrue("4".equals(result.getData()) || "6".equals(result.getData())); resetRequestCount(); // Test the retry be triggered diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/AbstractXdsHttpClientInterceptor.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/AbstractXdsHttpClientInterceptor.java index ee517c2217..94739479b0 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/AbstractXdsHttpClientInterceptor.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/AbstractXdsHttpClientInterceptor.java @@ -245,16 +245,15 @@ protected Optional chooseServiceInstanceForXds() { if (CollectionUtils.isEmpty(serviceInstanceSet)) { return Optional.empty(); } + removeCircuitBreakerInstance(scenarioInfo, serviceInstanceSet); if (RetryContext.INSTANCE.isPolicyNeedRetry()) { removeRetriedServiceInstance(serviceInstanceSet); } - removeCircuitBreakerInstance(scenarioInfo, serviceInstanceSet); return Optional.ofNullable(chooseServiceInstanceByLoadBalancer(serviceInstanceSet, scenarioInfo)); } private void removeRetriedServiceInstance(Set serviceInstanceSet) { RetryPolicy retryPolicy = RetryContext.INSTANCE.getRetryPolicy(); - retryPolicy.retryMark(); Set retriedInstance = retryPolicy.getAllRetriedInstance(); Set allInstance = new HashSet<>(serviceInstanceSet); for (Object retryInstance : retriedInstance) { @@ -271,7 +270,9 @@ private ServiceInstance chooseServiceInstanceByLoadBalancer(Set FlowControlScenario scenarioInfo) { XdsLoadBalancer loadBalancer = XdsLoadBalancerFactory.getLoadBalancer(scenarioInfo.getServiceName(), scenarioInfo.getClusterName()); - return loadBalancer.selectInstance(new ArrayList<>(instanceSet)); + ServiceInstance serviceInstance = loadBalancer.selectInstance(new ArrayList<>(instanceSet)); + RetryContext.INSTANCE.updateRetriedServiceInstance(serviceInstance); + return serviceInstance; } private void removeCircuitBreakerInstance(FlowControlScenario scenarioInfo, Set instanceSet) { diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java index da33153895..9d7261f888 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java @@ -57,8 +57,6 @@ public class DispatcherServletInterceptor extends InterceptorSupporter { private Function getRequestUri; - private Function getPathInfo; - private Function getMethod; private Function> getHeaderNames; @@ -89,11 +87,9 @@ private Optional convertToHttpEntity(Object request) { if (request == null) { return Optional.empty(); } - String uri = getRequestUri.apply(request); return Optional.of(new HttpRequestEntity.Builder() .setRequestType(RequestType.SERVER) - .setPathInfo(getPathInfo.apply(request)) - .setServletPath(uri) + .setApiPath(getRequestUri.apply(request)) .setHeaders(getHeaders(request)) .setMethod(getMethod.apply(request)) .setServiceName(getHeader.apply(request, ConfigConst.FLOW_REMOTE_SERVICE_NAME_HEADER_KEY)) @@ -232,7 +228,6 @@ private void initFunction() { boolean canLoadLowVersion = canLoadLowVersion(); if (canLoadLowVersion) { getRequestUri = obj -> ((HttpServletRequest) obj).getRequestURI(); - getPathInfo = obj -> ((HttpServletRequest) obj).getPathInfo(); getMethod = obj -> ((HttpServletRequest) obj).getMethod(); getHeaderNames = obj -> ((HttpServletRequest) obj).getHeaderNames(); getHeader = (obj, key) -> ((HttpServletRequest) obj).getHeader(key); @@ -246,7 +241,6 @@ private void initFunction() { setStatus = (obj, code) -> ((HttpServletResponse) obj).setStatus(code); } else { getRequestUri = this::getRequestUri; - getPathInfo = this::getPathInfo; getMethod = this::getMethod; getHeaderNames = this::getHeaderNames; getHeader = this::getHeader; diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttp3ClientDeclarer.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttp3ClientDeclarer.java index bcb0082679..3099f9fd06 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttp3ClientDeclarer.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttp3ClientDeclarer.java @@ -16,10 +16,10 @@ package io.sermant.flowcontrol.retry.client; -import io.sermant.core.plugin.agent.declarer.AbstractPluginDeclarer; import io.sermant.core.plugin.agent.declarer.InterceptDeclarer; import io.sermant.core.plugin.agent.matcher.ClassMatcher; import io.sermant.core.plugin.agent.matcher.MethodMatcher; +import io.sermant.flowcontrol.AbstractXdsDeclarer; /** * For OKHTTP requests, obtain the instance list from the registry to block them @@ -27,7 +27,7 @@ * @author zhp * @since 2024-12-20 */ -public class OkHttp3ClientDeclarer extends AbstractPluginDeclarer { +public class OkHttp3ClientDeclarer extends AbstractXdsDeclarer { /** * The fully qualified name of the enhanced okhttp request */ diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainDeclarer.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainDeclarer.java index a616fd169b..a3b0873724 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainDeclarer.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainDeclarer.java @@ -16,10 +16,10 @@ package io.sermant.flowcontrol.retry.client; -import io.sermant.core.plugin.agent.declarer.AbstractPluginDeclarer; import io.sermant.core.plugin.agent.declarer.InterceptDeclarer; import io.sermant.core.plugin.agent.matcher.ClassMatcher; import io.sermant.core.plugin.agent.matcher.MethodMatcher; +import io.sermant.flowcontrol.AbstractXdsDeclarer; /** * For OKHTTP requests, modify the URL of request @@ -27,7 +27,7 @@ * @author zhp * @since 2024-12-20 */ -public class OkHttpClientInterceptorChainDeclarer extends AbstractPluginDeclarer { +public class OkHttpClientInterceptorChainDeclarer extends AbstractXdsDeclarer { private static final String ENHANCE_CLASSES = "com.squareup.okhttp.Call$ApplicationInterceptorChain"; diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpClient4xInterceptorTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpClient4xInterceptorTest.java new file mode 100644 index 0000000000..1bc206ae21 --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpClient4xInterceptorTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.flowcontrol.retry.client; + + +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResponse; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.util.XdsThreadLocalUtil; +import io.sermant.flowcontrol.common.xds.circuit.XdsCircuitBreakerManager; +import io.sermant.flowcontrol.inject.ErrorCloseableHttpResponse; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; + +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpGet; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +/** + * HttpClient4xIntercepto test + * + * @author zhp + * @since 2025-04-11 + */ +public class HttpClient4xInterceptorTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic pluginServiceManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsHttpFlowControlService xdsHttpFlowControlService; + + private HttpGet httpRequest; + + private Method method = this.getClass().getMethod("getResult", null); + + private XdsFlowControlService xdsFlowControlService; + + public HttpClient4xInterceptorTest() throws NoSuchMethodException { + } + + @After + public void tearDown() { + pluginConfigManagerMockedStatic.close(); + pluginServiceManagerMockedStatic.close(); + serviceManagerMockedStatic.close(); + } + + /** + * preinitialization + * + * @throws Exception initialization failure thrown + */ + @Before + public void before() throws Exception { + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); + xdsHttpFlowControlService = Mockito.mock(XdsHttpFlowControlService.class); + pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(XdsHttpFlowControlService.class)) + .thenReturn(xdsHttpFlowControlService); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + method = Mockito.mock(Method.class); + httpRequest = new HttpGet(); + } + + @Test + public void test() throws NoSuchMethodException { + HttpClient4xInterceptor interceptor = new HttpClient4xInterceptor(); + // test parameter type is incorrect + ExecuteContext executeContext = buildErrorContext(); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test request URL does not meet the requirements + executeContext = buildContext(); + String resultMsg = "success"; + int code = HttpStatus.SC_BAD_REQUEST; + AtomicBoolean triggeredFlag = new AtomicBoolean(true); + doAnswer(invocation -> { + if (triggeredFlag.get()) { + Object[] args = invocation.getArguments(); + FlowControlResult flowControlResult = (FlowControlResult) args[1]; + flowControlResult.setSkip(true); + flowControlResult.setResponse(new FlowControlResponse(resultMsg, code)); + } + return null; + }).when(xdsHttpFlowControlService).onBefore(any(), any()); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test triggered flow control rules, abort the request + httpRequest.setURI(URI.create("http://provider:8080/path")); + interceptor.doBefore(executeContext); + Object result = executeContext.getResult(); + Assert.assertTrue(result instanceof ErrorCloseableHttpResponse); + ErrorCloseableHttpResponse response = (ErrorCloseableHttpResponse) result; + Assert.assertEquals(code, response.getStatusLine().getStatusCode()); + Assert.assertEquals(resultMsg, response.getStatusLine().getReasonPhrase()); + + // test trigger circuit breaker rules + triggeredFlag.set(false); + XdsRequestCircuitBreakers xdsRequestCircuitBreakers = new XdsRequestCircuitBreakers(); + Mockito.when(xdsFlowControlService.getRequestCircuitBreakers(any(), any())) + .thenReturn(Optional.of(xdsRequestCircuitBreakers)); + xdsRequestCircuitBreakers.setMaxRequests(1); + FlowControlScenario scenario = new FlowControlScenario(); + scenario.setClusterName("test"); + scenario.setServiceName("provider"); + XdsCircuitBreakerManager.incrementActiveRequests(scenario.getServiceName(), scenario.getClusterName()); + XdsThreadLocalUtil.setScenarioInfo(scenario); + interceptor.doBefore(executeContext); + result = executeContext.getResult(); + Assert.assertTrue(result instanceof ErrorCloseableHttpResponse); + response = (ErrorCloseableHttpResponse) result; + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.getStatusLine().getStatusCode()); + Assert.assertEquals("CircuitBreaker has forced open and deny any requests", + response.getStatusLine().getReasonPhrase()); + } + + private ExecuteContext buildContext() throws NoSuchMethodException { + httpRequest.setURI(URI.create("http://127.0.0.1:8080/path")); + return ExecuteContext.forMemberMethod(new HttpClient4xInterceptorTest(), method, new Object[]{null, httpRequest}, null, null); + } + + private ExecuteContext buildErrorContext() throws NoSuchMethodException { + return ExecuteContext.forMemberMethod(new HttpClient4xInterceptorTest(), method, new Object[]{null, ""}, null, null); + } + + public String getResult(){ + return "success"; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionConnectInterceptorTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionConnectInterceptorTest.java new file mode 100644 index 0000000000..84d0def434 --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionConnectInterceptorTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.flowcontrol.retry.client; + + +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResponse; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.util.XdsThreadLocalUtil; +import io.sermant.flowcontrol.common.xds.circuit.XdsCircuitBreakerManager; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; + +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +/** + * HttpUrlConnectionConnect test + * + * @author zhp + * @since 2025-04-11 + */ +public class HttpUrlConnectionConnectInterceptorTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic pluginServiceManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsHttpFlowControlService xdsHttpFlowControlService; + + private Method method = this.getClass().getMethod("getResult", null); + + private XdsFlowControlService xdsFlowControlService; + + public HttpUrlConnectionConnectInterceptorTest() throws NoSuchMethodException { + } + + @After + public void tearDown() { + pluginConfigManagerMockedStatic.close(); + pluginServiceManagerMockedStatic.close(); + serviceManagerMockedStatic.close(); + } + + /** + * preinitialization + * + * @throws Exception initialization failure thrown + */ + @Before + public void before() throws Exception { + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); + xdsHttpFlowControlService = Mockito.mock(XdsHttpFlowControlService.class); + pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(XdsHttpFlowControlService.class)) + .thenReturn(xdsHttpFlowControlService); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + method = Mockito.mock(Method.class); + } + + @Test + public void test() throws Exception { + HttpUrlConnectionConnectInterceptor interceptor = new HttpUrlConnectionConnectInterceptor(); + // test parameter type is incorrect + ExecuteContext executeContext = buildErrorContext(); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test request URL does not meet the requirements + executeContext = buildContext("http://localhost:8080/path"); + String resultMsg = "success"; + int code = HttpStatus.SC_BAD_REQUEST; + AtomicBoolean triggeredFlag = new AtomicBoolean(true); + doAnswer(invocation -> { + if (triggeredFlag.get()) { + Object[] args = invocation.getArguments(); + FlowControlResult flowControlResult = (FlowControlResult) args[1]; + flowControlResult.setSkip(true); + flowControlResult.setResponse(new FlowControlResponse(resultMsg, code)); + } + return null; + }).when(xdsHttpFlowControlService).onBefore(any(), any()); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test triggered flow control rules, abort the request + executeContext = buildContext("http://provider:8080/path"); + interceptor.doBefore(executeContext); + HttpURLConnection connection = (HttpURLConnection) executeContext.getObject(); + Assert.assertEquals(code, connection.getResponseCode()); + Assert.assertEquals(resultMsg, connection.getResponseMessage()); + + // test trigger circuit breaker rules + triggeredFlag.set(false); + XdsRequestCircuitBreakers xdsRequestCircuitBreakers = new XdsRequestCircuitBreakers(); + Mockito.when(xdsFlowControlService.getRequestCircuitBreakers(any(), any())) + .thenReturn(Optional.of(xdsRequestCircuitBreakers)); + xdsRequestCircuitBreakers.setMaxRequests(1); + FlowControlScenario scenario = new FlowControlScenario(); + scenario.setClusterName("test"); + scenario.setServiceName("provider"); + XdsCircuitBreakerManager.incrementActiveRequests(scenario.getServiceName(), scenario.getClusterName()); + XdsThreadLocalUtil.setScenarioInfo(scenario); + interceptor.doBefore(executeContext); + connection = (HttpURLConnection) executeContext.getObject(); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, connection.getResponseCode()); + Assert.assertEquals("CircuitBreaker has forced open and deny any requests", + connection.getResponseMessage()); + } + + private ExecuteContext buildContext(String urlStr) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + return ExecuteContext.forMemberMethod(connection, method, new Object[]{null}, null, null); + } + + private ExecuteContext buildErrorContext() { + HttpURLConnection connection = Mockito.mock(HttpURLConnection.class); + Mockito.when(connection.getURL()).thenReturn(null); + return ExecuteContext.forMemberMethod(connection, method, new Object[]{null}, null, null); + } + + public String getResult(){ + return "success"; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionResponseStreamInterceptorTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionResponseStreamInterceptorTest.java new file mode 100644 index 0000000000..3417964bc7 --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/HttpUrlConnectionResponseStreamInterceptorTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.flowcontrol.retry.client; + + +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResponse; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; + +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +/** + * HttpUrlConnectionConnect test + * + * @author zhp + * @since 2025-04-11 + */ +public class HttpUrlConnectionResponseStreamInterceptorTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic pluginServiceManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsHttpFlowControlService xdsHttpFlowControlService; + + private Method method = this.getClass().getMethod("getResult", null); + + private XdsFlowControlService xdsFlowControlService; + + public HttpUrlConnectionResponseStreamInterceptorTest() throws NoSuchMethodException { + } + + @After + public void tearDown() { + pluginConfigManagerMockedStatic.close(); + pluginServiceManagerMockedStatic.close(); + serviceManagerMockedStatic.close(); + } + + /** + * preinitialization + * + * @throws Exception initialization failure thrown + */ + @Before + public void before() throws Exception { + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); + xdsHttpFlowControlService = Mockito.mock(XdsHttpFlowControlService.class); + pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(XdsHttpFlowControlService.class)) + .thenReturn(xdsHttpFlowControlService); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + method = Mockito.mock(Method.class); + } + + @Test + public void test() throws Exception { + HttpUrlConnectionResponseStreamInterceptor interceptor = new HttpUrlConnectionResponseStreamInterceptor(); + // test parameter type is incorrect + ExecuteContext executeContext = buildErrorContext(); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test request URL does not meet the requirements + executeContext = buildContext("http://localhost:8080/path"); + String resultMsg = "success"; + int code = HttpStatus.SC_BAD_REQUEST; + AtomicBoolean triggeredFlag = new AtomicBoolean(true); + doAnswer(invocation -> { + if (triggeredFlag.get()) { + Object[] args = invocation.getArguments(); + FlowControlResult flowControlResult = (FlowControlResult) args[1]; + flowControlResult.setSkip(true); + flowControlResult.setResponse(new FlowControlResponse(resultMsg, code)); + } + return null; + }).when(xdsHttpFlowControlService).onBefore(any(), any()); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + } + + private ExecuteContext buildContext(String urlStr) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + return ExecuteContext.forMemberMethod(connection, method, new Object[]{null}, null, null); + } + + private ExecuteContext buildErrorContext() { + HttpURLConnection connection = Mockito.mock(HttpURLConnection.class); + Mockito.when(connection.getURL()).thenReturn(null); + return ExecuteContext.forMemberMethod(connection, method, new Object[]{null}, null, null); + } + + public String getResult(){ + return "success"; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttp3InterceptorTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttp3InterceptorTest.java new file mode 100644 index 0000000000..b0fd0d303b --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttp3InterceptorTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.flowcontrol.retry.client; + + +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResponse; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.util.XdsThreadLocalUtil; +import io.sermant.flowcontrol.common.xds.circuit.XdsCircuitBreakerManager; +import io.sermant.flowcontrol.inject.ErrorCloseableHttpResponse; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; +import okhttp3.*; +import okhttp3.internal.connection.RealCall; +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +/** + * OkHttp3Interceptor test + * + * @author zhp + * @since 2025-04-11 + */ +public class OkHttp3InterceptorTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic pluginServiceManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsHttpFlowControlService xdsHttpFlowControlService; + + private Request httpRequest; + + private Method method = this.getClass().getMethod("getResult", null); + + private XdsFlowControlService xdsFlowControlService; + + public OkHttp3InterceptorTest() throws NoSuchMethodException { + } + + @After + public void tearDown() { + pluginConfigManagerMockedStatic.close(); + pluginServiceManagerMockedStatic.close(); + serviceManagerMockedStatic.close(); + } + + /** + * preinitialization + * + * @throws Exception initialization failure thrown + */ + @Before + public void before() throws Exception { + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); + xdsHttpFlowControlService = Mockito.mock(XdsHttpFlowControlService.class); + pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(XdsHttpFlowControlService.class)) + .thenReturn(xdsHttpFlowControlService); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + method = Mockito.mock(Method.class); + } + + @Test + public void test() throws Exception { + OkHttp3ClientInterceptor interceptor = new OkHttp3ClientInterceptor(); + // test parameter type is incorrect + ExecuteContext executeContext = buildErrorContext(); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test request URL does not meet the requirements + executeContext = buildContext("http://localhost:8080/path"); + String resultMsg = "success"; + int code = HttpStatus.SC_BAD_REQUEST; + AtomicBoolean triggeredFlag = new AtomicBoolean(true); + doAnswer(invocation -> { + if (triggeredFlag.get()) { + Object[] args = invocation.getArguments(); + FlowControlResult flowControlResult = (FlowControlResult) args[1]; + flowControlResult.setSkip(true); + flowControlResult.setResponse(new FlowControlResponse(resultMsg, code)); + } + return null; + }).when(xdsHttpFlowControlService).onBefore(any(), any()); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test triggered flow control rules, abort the request + executeContext = buildContext("http://provider:8080/path"); + interceptor.doBefore(executeContext); + Object result = executeContext.getResult(); + Assert.assertTrue(result instanceof Response); + Response response = (Response) result; + Assert.assertEquals(code, response.code()); + Assert.assertEquals(resultMsg, response.message()); + + // test trigger circuit breaker rules + triggeredFlag.set(false); + XdsRequestCircuitBreakers xdsRequestCircuitBreakers = new XdsRequestCircuitBreakers(); + Mockito.when(xdsFlowControlService.getRequestCircuitBreakers(any(), any())) + .thenReturn(Optional.of(xdsRequestCircuitBreakers)); + xdsRequestCircuitBreakers.setMaxRequests(1); + FlowControlScenario scenario = new FlowControlScenario(); + scenario.setClusterName("test"); + scenario.setServiceName("provider"); + XdsCircuitBreakerManager.incrementActiveRequests(scenario.getServiceName(), scenario.getClusterName()); + XdsThreadLocalUtil.setScenarioInfo(scenario); + interceptor.doBefore(executeContext); + result = executeContext.getResult(); + Assert.assertTrue(result instanceof Response); + response = (Response) result; + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.code()); + Assert.assertEquals("CircuitBreaker has forced open and deny any requests", + response.message()); + } + + private ExecuteContext buildContext(String url) { + httpRequest = new okhttp3.Request.Builder().url(url).build(); + OkHttpClient client = new OkHttpClient(); + Call call = client.newCall(httpRequest); + return ExecuteContext.forMemberMethod(call, method, new Object[]{null, httpRequest}, null, null); + } + + private ExecuteContext buildErrorContext() { + Call call = Mockito.mock(Call.class); + return ExecuteContext.forMemberMethod(call, method, new Object[]{null, ""}, null, null); + } + + public String getResult(){ + return "success"; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainInterceptorTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainInterceptorTest.java new file mode 100644 index 0000000000..6d5e139e71 --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/test/java/io/sermant/flowcontrol/retry/client/OkHttpClientInterceptorChainInterceptorTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.flowcontrol.retry.client; + + +import com.squareup.okhttp.Call; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.XdsRequestCircuitBreakers; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResponse; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.util.XdsThreadLocalUtil; +import io.sermant.flowcontrol.common.xds.circuit.XdsCircuitBreakerManager; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; + +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; + +/** + * OkHttpClientInterceptorChainInterceptor test + * + * @author zhp + * @since 2025-04-11 + */ +public class OkHttpClientInterceptorChainInterceptorTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic pluginServiceManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsHttpFlowControlService xdsHttpFlowControlService; + + private Request httpRequest; + + private Method method = this.getClass().getMethod("getResult", null); + + private XdsFlowControlService xdsFlowControlService; + + public OkHttpClientInterceptorChainInterceptorTest() throws NoSuchMethodException { + } + + @After + public void tearDown() { + pluginConfigManagerMockedStatic.close(); + pluginServiceManagerMockedStatic.close(); + serviceManagerMockedStatic.close(); + } + + /** + * preinitialization + * + * @throws Exception initialization failure thrown + */ + @Before + public void before() throws Exception { + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); + xdsHttpFlowControlService = Mockito.mock(XdsHttpFlowControlService.class); + pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(XdsHttpFlowControlService.class)) + .thenReturn(xdsHttpFlowControlService); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + method = Mockito.mock(Method.class); + } + + @Test + public void test() throws Exception { + OkHttpClientInterceptorChainInterceptor interceptor = new OkHttpClientInterceptorChainInterceptor(); + // test parameter type is incorrect + ExecuteContext executeContext = buildErrorContext(); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test request URL does not meet the requirements + executeContext = buildContext("http://localhost:8080/path"); + String resultMsg = "success"; + int code = HttpStatus.SC_BAD_REQUEST; + AtomicBoolean triggeredFlag = new AtomicBoolean(true); + doAnswer(invocation -> { + if (triggeredFlag.get()) { + Object[] args = invocation.getArguments(); + FlowControlResult flowControlResult = (FlowControlResult) args[1]; + flowControlResult.setSkip(true); + flowControlResult.setResponse(new FlowControlResponse(resultMsg, code)); + } + return null; + }).when(xdsHttpFlowControlService).onBefore(any(), any()); + interceptor.doBefore(executeContext); + Assert.assertNull(executeContext.getResult()); + + // test triggered flow control rules, abort the request + executeContext = buildContext("http://provider:8080/path"); + interceptor.doBefore(executeContext); + Object result = executeContext.getResult(); + Assert.assertTrue(result instanceof Response); + Response response = (Response) result; + Assert.assertEquals(code, response.code()); + Assert.assertEquals(resultMsg, response.message()); + + // test trigger circuit breaker rules + triggeredFlag.set(false); + XdsRequestCircuitBreakers xdsRequestCircuitBreakers = new XdsRequestCircuitBreakers(); + Mockito.when(xdsFlowControlService.getRequestCircuitBreakers(any(), any())) + .thenReturn(Optional.of(xdsRequestCircuitBreakers)); + xdsRequestCircuitBreakers.setMaxRequests(1); + FlowControlScenario scenario = new FlowControlScenario(); + scenario.setClusterName("test"); + scenario.setServiceName("provider"); + XdsCircuitBreakerManager.incrementActiveRequests(scenario.getServiceName(), scenario.getClusterName()); + XdsThreadLocalUtil.setScenarioInfo(scenario); + interceptor.doBefore(executeContext); + result = executeContext.getResult(); + Assert.assertTrue(result instanceof Response); + response = (Response) result; + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, response.code()); + Assert.assertEquals("CircuitBreaker has forced open and deny any requests", + response.message()); + } + + private ExecuteContext buildContext(String url) { + httpRequest = new Request.Builder().url(url).build(); + OkHttpClient client = new OkHttpClient(); + Call call = client.newCall(httpRequest); + return ExecuteContext.forMemberMethod(call, method, new Object[]{httpRequest}, null, null); + } + + private ExecuteContext buildErrorContext() { + Call call = Mockito.mock(Call.class); + return ExecuteContext.forMemberMethod(call, method, new Object[]{""}, null, null); + } + + public String getResult(){ + return "success"; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsFaultRequestHandlerTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsFaultRequestHandlerTest.java new file mode 100644 index 0000000000..5497426afd --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsFaultRequestHandlerTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package io.sermant.flowcontrol.res4j.chain.handler; + +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.*; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.config.XdsFlowControlConfig; +import io.sermant.flowcontrol.common.core.rule.fault.FaultException; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.entity.HttpRequestEntity; +import io.sermant.flowcontrol.res4j.chain.context.ChainContext; +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; + +/** + * XdsFaultRequestHandler test + * + * @author zhp + * @since 2025-04-12 + */ +public class XdsFaultRequestHandlerTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsFlowControlService xdsFlowControlService; + + @Before + public void setUp() { + pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + XdsFlowControlConfig xdsFlowControlConfig = new XdsFlowControlConfig(); + xdsFlowControlConfig.setEnable(true); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(XdsFlowControlConfig.class)) + .thenReturn(xdsFlowControlConfig); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + XdsHttpFault xdsHttpFault = new XdsHttpFault(); + Mockito.when(xdsFlowControlService.getHttpFault(any(), any())).thenReturn(Optional.of(xdsHttpFault)); + XdsAbort xdsAbort = new XdsAbort(); + FractionalPercent fractionalPercent = new FractionalPercent(); + fractionalPercent.setNumerator(100); + fractionalPercent.setDenominator(100); + xdsAbort.setPercentage(fractionalPercent); + xdsAbort.setHttpStatus(HttpStatus.SC_BAD_REQUEST); + xdsHttpFault.setAbort(xdsAbort); + } + + /** + * test process + */ + @Test + public void test() { + XdsFaultRequestHandler xdsFaultRequestHandler = new XdsFaultRequestHandler(); + FlowControlScenario scenarioInfo = new FlowControlScenario(); + scenarioInfo.setClusterName("outbound|8080||serviceA.default.svc.cluster.local"); + scenarioInfo.setRouteName("router"); + scenarioInfo.setServiceName("service"); + Assert.assertThrows(FaultException.class, + () -> xdsFaultRequestHandler.onBefore(new HttpRequestEntity(), scenarioInfo)); + } + + @After + public void clear() { + pluginConfigManagerMockedStatic.close(); + ChainContext.remove(); + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsRateLimitRequestHandlerTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsRateLimitRequestHandlerTest.java new file mode 100644 index 0000000000..4a672e08ea --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/chain/handler/XdsRateLimitRequestHandlerTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package io.sermant.flowcontrol.res4j.chain.handler; + +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.service.ServiceManager; +import io.sermant.core.service.xds.XdsCoreService; +import io.sermant.core.service.xds.XdsFlowControlService; +import io.sermant.core.service.xds.entity.FractionalPercent; +import io.sermant.core.service.xds.entity.XdsRateLimit; +import io.sermant.core.service.xds.entity.XdsTokenBucket; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.config.XdsFlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.entity.HttpRequestEntity; +import io.sermant.flowcontrol.res4j.chain.context.ChainContext; +import io.sermant.flowcontrol.res4j.exceptions.RateLimitException; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; + +/** + * XdsRateLimitRequestHandler test + * + * @author zhp + * @since 2025-04-12 + */ +public class XdsRateLimitRequestHandlerTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic serviceManagerMockedStatic; + + private XdsFlowControlService xdsFlowControlService; + + @Before + public void setUp() { + pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + XdsFlowControlConfig xdsFlowControlConfig = new XdsFlowControlConfig(); + xdsFlowControlConfig.setEnable(true); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(XdsFlowControlConfig.class)) + .thenReturn(xdsFlowControlConfig); + serviceManagerMockedStatic = Mockito.mockStatic(ServiceManager.class); + XdsCoreService xdsCoreService = Mockito.mock(XdsCoreService.class); + serviceManagerMockedStatic.when(() -> ServiceManager.getService(XdsCoreService.class)).thenReturn(xdsCoreService); + xdsFlowControlService = Mockito.mock(XdsFlowControlService.class); + Mockito.when(xdsCoreService.getXdsFlowControlService()).thenReturn(xdsFlowControlService); + XdsRateLimit xdsRateLimit = new XdsRateLimit(); + Mockito.when(xdsFlowControlService.getRateLimit(any(), any(), any())).thenReturn(Optional.of(xdsRateLimit)); + XdsTokenBucket tokenBucket = new XdsTokenBucket(); + FractionalPercent fractionalPercent = new FractionalPercent(); + fractionalPercent.setNumerator(100); + fractionalPercent.setDenominator(100); + xdsRateLimit.setTokenBucket(tokenBucket); + xdsRateLimit.setPercent(fractionalPercent); + tokenBucket.setMaxTokens(1); + tokenBucket.setFillInterval(10000); + tokenBucket.setTokensPerFill(1); + } + + /** + * test process + */ + @Test + public void test() { + XdsRateLimitRequestHandler xdsRateLimitRequestHandler = new XdsRateLimitRequestHandler(); + FlowControlScenario scenarioInfo = new FlowControlScenario(); + scenarioInfo.setClusterName("outbound|8080||serviceA.default.svc.cluster.local"); + scenarioInfo.setRouteName("router"); + scenarioInfo.setServiceName("service"); + assertDoesNotThrow(() -> xdsRateLimitRequestHandler.onBefore(new HttpRequestEntity(), scenarioInfo)); + Assert.assertThrows(RateLimitException.class, + () -> xdsRateLimitRequestHandler.onBefore(new HttpRequestEntity(), scenarioInfo)); + } + + @After + public void clear() { + pluginConfigManagerMockedStatic.close(); + ChainContext.remove(); + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/service/XdsFlowControlServiceImplTest.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/service/XdsFlowControlServiceImplTest.java new file mode 100644 index 0000000000..ba2850201c --- /dev/null +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-service/src/test/java/io/sermant/flowcontrol/res4j/service/XdsFlowControlServiceImplTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package io.sermant.flowcontrol.res4j.service; + +import io.sermant.core.operation.OperationManager; +import io.sermant.core.operation.converter.api.YamlConverter; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.flowcontrol.common.config.FlowControlConfig; +import io.sermant.flowcontrol.common.config.XdsFlowControlConfig; +import io.sermant.flowcontrol.common.entity.FlowControlResult; +import io.sermant.flowcontrol.common.entity.FlowControlScenario; +import io.sermant.flowcontrol.common.entity.HttpRequestEntity; +import io.sermant.flowcontrol.common.entity.RequestEntity; +import io.sermant.flowcontrol.common.util.XdsThreadLocalUtil; +import io.sermant.flowcontrol.res4j.chain.context.ChainContext; +import io.sermant.flowcontrol.service.rest4j.XdsHttpFlowControlService; +import io.sermant.implement.operation.converter.YamlConverterImpl; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +/** + * XdsFlowControlServiceImpl test + * + * @author zhp + * @since 2025-04-12 + */ +public class XdsFlowControlServiceImplTest { + private MockedStatic pluginConfigManagerMockedStatic; + + private MockedStatic operationManagerMockedStatic; + + @Before + public void setUp() { + pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(FlowControlConfig.class)) + .thenReturn(new FlowControlConfig()); + XdsFlowControlConfig xdsFlowControlConfig = new XdsFlowControlConfig(); + xdsFlowControlConfig.setEnable(true); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(XdsFlowControlConfig.class)) + .thenReturn(xdsFlowControlConfig); + operationManagerMockedStatic = Mockito.mockStatic(OperationManager.class); + operationManagerMockedStatic.when(() -> OperationManager.getOperation(YamlConverter.class)).thenReturn(new YamlConverterImpl()); + } + + /** + * test process + */ + @Test + public void test() { + final XdsHttpFlowControlService xdsHttpFlowControlService = new XdsHttpFlowControlServiceImpl(); + final FlowControlResult flowControlResult = new FlowControlResult(); + final FlowControlScenario flowControlScenario = new FlowControlScenario(); + HttpRequestEntity httpRequestEntity = new HttpRequestEntity(); + httpRequestEntity.setRequestType(RequestEntity.RequestType.CLIENT); + xdsHttpFlowControlService.onBefore(httpRequestEntity, flowControlResult); + xdsHttpFlowControlService.onThrow(httpRequestEntity, new Exception("error"), flowControlScenario); + xdsHttpFlowControlService.onAfter(httpRequestEntity, new Object(), flowControlScenario); + Assert.assertNotNull(XdsThreadLocalUtil.getScenarioInfo()); + } + + @After + public void clear() { + pluginConfigManagerMockedStatic.close(); + operationManagerMockedStatic.close(); + ChainContext.remove(); + } +}