diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d617662..c238cd28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - [feat: support tsf 2024.](https://github.com/Tencent/spring-cloud-tencent/pull/1635) - [fix: fix ConfigChangeListener and unit test](https://github.com/Tencent/spring-cloud-tencent/pull/1657) - [fix: fix ConfigChangeListener ut bug](https://github.com/Tencent/spring-cloud-tencent/pull/1663) +- [feat: support shortest response time lb and least connection lb](https://github.com/Tencent/spring-cloud-tencent/pull/1637) diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/AbstractPolarisLoadBalancer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/AbstractPolarisLoadBalancer.java index d59967489..72b0d14ca 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/AbstractPolarisLoadBalancer.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/AbstractPolarisLoadBalancer.java @@ -88,6 +88,7 @@ public abstract class AbstractPolarisLoadBalancer implements ReactorServiceInsta try { ProcessLoadBalanceResponse response = routerAPI.processLoadBalance(req); + log.debug("loadbalancer choose:" + response.getTargetInstance().getHost() + ":" + response.getTargetInstance().getPort()); return new DefaultResponse(new PolarisServiceInstance(response.getTargetInstance())); } catch (Exception e) { diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancer.java new file mode 100644 index 000000000..cced442d0 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancer.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import com.tencent.polaris.api.config.consumer.LoadBalanceConfig; +import com.tencent.polaris.api.rpc.Criteria; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceRequest; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; + +/** + * Polaris Least Connection LoadBalancer. + * + * @author Yuwei Fu + */ +public class PolarisLeastConnectionLoadBalancer extends AbstractPolarisLoadBalancer { + public PolarisLeastConnectionLoadBalancer(String serviceId, ObjectProvider supplierObjectProvider, RouterAPI routerAPI) { + super(serviceId, supplierObjectProvider, routerAPI); + } + + @Override + protected ProcessLoadBalanceRequest setProcessLoadBalanceRequest(ProcessLoadBalanceRequest req) { + req.setLbPolicy(LoadBalanceConfig.LOAD_BALANCE_LEAST_CONNECTION); + req.setCriteria(new Criteria()); + return req; + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.java index ee78ea5a6..149e21441 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.java @@ -20,11 +20,13 @@ package com.tencent.cloud.polaris.loadbalancer; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** @@ -33,12 +35,16 @@ import org.springframework.context.annotation.Configuration; * @author Haotian Zhang */ @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties +@EnableConfigurationProperties(PolarisLoadBalancerProperties.class) @ConditionalOnDiscoveryEnabled @ConditionalOnPolarisEnabled @ConditionalOnProperty(value = "spring.cloud.polaris.loadbalancer.enabled", matchIfMissing = true) @AutoConfigureAfter(LoadBalancerAutoConfiguration.class) @LoadBalancerClients(defaultConfiguration = PolarisLoadBalancerClientConfiguration.class) public class PolarisLoadBalancerAutoConfiguration { - + @Bean + @ConditionalOnMissingBean + public PolarisShortestResponseTimeLoadBalancerConfigModifier polarisShortestResponseTimeLoadBalancerConfigModifier(PolarisLoadBalancerProperties polarisLoadBalancerProperties) { + return new PolarisShortestResponseTimeLoadBalancerConfigModifier(polarisLoadBalancerProperties.shortestResponseTime); + } } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java index 3f26bfbff..0d36c4f27 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerClientConfiguration.java @@ -98,7 +98,25 @@ public class PolarisLoadBalancerClientConfiguration { return new PolarisRingHashLoadBalancer(name, loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), polarisSDKContextManager.getRouterAPI()); } + @Bean + @ConditionalOnMissingBean + @Conditional(PolarisShortestResponseTimeStrategyCondition.class) + public ReactorLoadBalancer polarisShortestResponseTimeLoadBalancer(Environment environment, + LoadBalancerClientFactory loadBalancerClientFactory, PolarisSDKContextManager polarisSDKContextManager) { + String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); + return new PolarisShortestResponseTimeLoadBalancer(name, + loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), polarisSDKContextManager.getRouterAPI()); + } + @Bean + @ConditionalOnMissingBean + @Conditional(PolarisLeastConnectionStrategyCondition.class) + public ReactorLoadBalancer polarisLeastConnectionLoadBalancer(Environment environment, + LoadBalancerClientFactory loadBalancerClientFactory, PolarisSDKContextManager polarisSDKContextManager) { + String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); + return new PolarisLeastConnectionLoadBalancer(name, + loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), polarisSDKContextManager.getRouterAPI()); + } @Bean @ConditionalOnMissingBean @Conditional(DefaultStrategyCondition.class) @@ -140,12 +158,31 @@ public class PolarisLoadBalancerClientConfiguration { } } + static class DefaultStrategyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return LoadBalancerEnvironmentPropertyUtils.equalToOrMissingForClientOrDefault(context.getEnvironment(), - "strategies", "default"); + "strategies", "polarisWeightedRoundRobin"); + } + } + + static class PolarisShortestResponseTimeStrategyCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return LoadBalancerEnvironmentPropertyUtils.equalToForClientOrDefault(context.getEnvironment(), + "strategies", "polarisShortestResponseTime"); + } + } + + static class PolarisLeastConnectionStrategyCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return LoadBalancerEnvironmentPropertyUtils.equalToForClientOrDefault(context.getEnvironment(), + "strategies", "polarisLeastConnection"); } } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerProperties.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerProperties.java new file mode 100644 index 000000000..b0b1ce4a3 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerProperties.java @@ -0,0 +1,66 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("spring.cloud.polaris.loadbalancer") +public class PolarisLoadBalancerProperties { + + /** + * Shortest response time load balancer. + */ + public ShortestResponseTime shortestResponseTime = new ShortestResponseTime(); + + ShortestResponseTime getShortestResponseTime() { + return shortestResponseTime; + } + + void setShortestResponseTime(ShortestResponseTime shortestResponseTime) { + this.shortestResponseTime = shortestResponseTime; + } + + @Override + public String toString() { + return "PolarisLoadBalancerProperties{" + + "shortestResponseTime=" + shortestResponseTime + + '}'; + } + + public static class ShortestResponseTime { + /** + * Slide period in milliseconds. Default is 30s. + */ + private long slidePeriod = 30000; + + long getSlidePeriod() { + return slidePeriod; + } + + void setSlidePeriod(long slidePeriod) { + this.slidePeriod = slidePeriod; + } + + @Override + public String toString() { + return "ShortestResponseTime{" + + "slidePeriod=" + slidePeriod + + '}'; + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancer.java new file mode 100644 index 000000000..c6034ad16 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancer.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import com.tencent.polaris.api.config.consumer.LoadBalanceConfig; +import com.tencent.polaris.api.rpc.Criteria; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceRequest; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; + +/** + * PolarisShortestResponseTimeLoadBalancer. + * + * @author Yuwei Fu + */ +public class PolarisShortestResponseTimeLoadBalancer extends AbstractPolarisLoadBalancer { + public PolarisShortestResponseTimeLoadBalancer(String serviceId, ObjectProvider supplierObjectProvider, RouterAPI routerAPI) { + super(serviceId, supplierObjectProvider, routerAPI); + } + + @Override + protected ProcessLoadBalanceRequest setProcessLoadBalanceRequest(ProcessLoadBalanceRequest req) { + req.setLbPolicy(LoadBalanceConfig.LOAD_BALANCE_SHORTEST_RESPONSE_TIME); + req.setCriteria(new Criteria()); + return req; + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerConfigModifier.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerConfigModifier.java new file mode 100644 index 000000000..fe7cdfeac --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerConfigModifier.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import com.tencent.cloud.common.constant.OrderConstant; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.polaris.api.config.consumer.LoadBalanceConfig; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import com.tencent.polaris.plugins.loadbalancer.shortestresponsetime.ShortestResponseTimeLoadBalanceConfig; + +public class PolarisShortestResponseTimeLoadBalancerConfigModifier implements PolarisConfigModifier { + + private final PolarisLoadBalancerProperties.ShortestResponseTime shortestResponseTime; + + public PolarisShortestResponseTimeLoadBalancerConfigModifier( + PolarisLoadBalancerProperties.ShortestResponseTime shortestResponseTime) { + this.shortestResponseTime = shortestResponseTime; + } + + @Override + public void modify(ConfigurationImpl configuration) { + ShortestResponseTimeLoadBalanceConfig config = configuration.getConsumer().getLoadbalancer() + .getPluginConfig(LoadBalanceConfig.LOAD_BALANCE_SHORTEST_RESPONSE_TIME, ShortestResponseTimeLoadBalanceConfig.class); + config.setSlidePeriod(shortestResponseTime.getSlidePeriod()); + configuration.getConsumer().getLoadbalancer() + .setPluginConfig(LoadBalanceConfig.LOAD_BALANCE_SHORTEST_RESPONSE_TIME, + config); + } + + @Override + public int getOrder() { + return OrderConstant.Modifier.LOAD_BALANCER_CONFIG_ORDER; + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json index aa4fe5f87..e62141b85 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -152,12 +152,34 @@ "type": "java.lang.String", "description": "the nacos authentication cluster-name.", "sourceType": "com.tencent.cloud.polaris.extend.nacos.NacosContextProperties" + }, + { + "name": "spring.cloud.polaris.loadbalancer.enabled", + "type": "java.lang.Boolean", + "defaultValue": "true", + "description": "polaris loadbalancer." + }, + { + "name": "spring.cloud.loadbalancer.strategy", + "type": "java.lang.String", + "defaultValue": "polarisWeightedRoundRobin", + "description": "loadbalancer strategy." + }, + { + "name": "spring.cloud.polaris.loadbalancer.shortest-response-time.slide-period", + "type": "java.lang.Long", + "defaultValue": 30000, + "description": "The length of the average response time calculation window for instances in milliseconds. The default is 30000 milliseconds." } ], "hints": [ { - "name": "spring.cloud.polaris.loadbalancer.strategy", + "name": "spring.cloud.loadbalancer.strategy", "values": [ + { + "value": "polarisWeightedRoundRobin", + "description": "polaris weighted round robin load balancer." + }, { "value": "roundRobin", "description": "round robin load balancer." @@ -170,9 +192,17 @@ "value": "polarisWeightedRandom", "description": "polaris weighted random load balancer." }, + { + "value": "polarisShortestResponseTime", + "description": "polaris shortest response time load balancer." + }, { "value": "polarisRingHash", "description": "polaris ring hash load balancer." + }, + { + "value": "polarisLeastConnection", + "description": "polaris least connection load balancer." } ] } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancerTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancerTest.java new file mode 100644 index 000000000..de1593548 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisLeastConnectionLoadBalancerTest.java @@ -0,0 +1,171 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import java.util.ArrayList; +import java.util.List; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.pojo.PolarisServiceInstance; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.polaris.api.exception.ErrorCode; +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.context.ApplicationContext; + +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE; +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_SERVICE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * PolarisShortestResponseTimeLoadBalancerTest. + * + * @author Yuwei Fu + */ +@ExtendWith(MockitoExtension.class) +public class PolarisLeastConnectionLoadBalancerTest { + + private static MockedStatic mockedApplicationContextAwareUtils; + private static Instance testInstance; + @Mock + private RouterAPI routerAPI; + @Mock + private ObjectProvider supplierObjectProvider; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext) + .thenReturn(applicationContext); + testInstance = Instance.createDefaultInstance("instance-id", LOCAL_NAMESPACE, + LOCAL_SERVICE, "host", 8090); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @Test + public void chooseNormalLogicTest_thenReturnAvailablePolarisInstance() { + Request request = Mockito.mock(Request.class); + List mockInstanceList = new ArrayList<>(); + mockInstanceList.add(new PolarisServiceInstance(testInstance)); + + ServiceInstanceListSupplier serviceInstanceListSupplier = Mockito.mock(ServiceInstanceListSupplier.class); + when(serviceInstanceListSupplier.get(request)).thenReturn(Flux.just(mockInstanceList)); + + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(serviceInstanceListSupplier); + + ProcessLoadBalanceResponse mockLbRes = new ProcessLoadBalanceResponse(testInstance); + when(routerAPI.processLoadBalance(any())).thenReturn(mockLbRes); + + // request construct and execute invoke + PolarisLeastConnectionLoadBalancer polarisLeastConnectionLoadBalancer = new PolarisLeastConnectionLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = polarisLeastConnectionLoadBalancer.choose(request); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + // verify method has invoked + verify(supplierObjectProvider).getIfAvailable(any()); + + //result assert + Assertions.assertThat(serviceInstance).isNotNull(); + Assertions.assertThat(serviceInstance instanceof PolarisServiceInstance).isTrue(); + + PolarisServiceInstance polarisServiceInstance = (PolarisServiceInstance) serviceInstance; + + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getId()).isEqualTo("instance-id"); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getNamespace()).isEqualTo(LOCAL_NAMESPACE); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getService()).isEqualTo(LOCAL_SERVICE); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getHost()).isEqualTo("host"); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getPort()).isEqualTo(8090); + } + + @Test + public void chooseExceptionTest_thenReturnEmptyInstance() { + + Request request = Mockito.mock(Request.class); + List mockInstanceList = new ArrayList<>(); + mockInstanceList.add(new PolarisServiceInstance(testInstance)); + + ServiceInstanceListSupplier serviceInstanceListSupplier = Mockito.mock(ServiceInstanceListSupplier.class); + when(serviceInstanceListSupplier.get(request)).thenReturn(Flux.just(mockInstanceList)); + + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(serviceInstanceListSupplier); + + when(routerAPI.processLoadBalance(any())).thenThrow(new PolarisException(ErrorCode.API_TIMEOUT)); + + // request construct and execute invoke + PolarisLeastConnectionLoadBalancer polarisLeastConnectionLoadBalancer = new PolarisLeastConnectionLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = polarisLeastConnectionLoadBalancer.choose(request); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + // verify method has invoked + verify(supplierObjectProvider).getIfAvailable(any()); + + //result assert + Assertions.assertThat(serviceInstance).isNull(); + } + + @Test + public void chooseEmptySupplierTest_thenReturnEmptyInstance() { + ServiceInstanceListSupplier noopSupplier = new NoopServiceInstanceListSupplier(); + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(noopSupplier); + + // request construct and execute invoke + PolarisLeastConnectionLoadBalancer polarisLeastConnectionLoadBalancer = new PolarisLeastConnectionLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = polarisLeastConnectionLoadBalancer.choose(); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + //result assert + Assertions.assertThat(serviceInstance).isNull(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerTest.java new file mode 100644 index 000000000..9323f7ba5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisShortestResponseTimeLoadBalancerTest.java @@ -0,0 +1,171 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.loadbalancer; + +import java.util.ArrayList; +import java.util.List; + +import com.tencent.cloud.common.metadata.StaticMetadataManager; +import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.pojo.PolarisServiceInstance; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.polaris.api.exception.ErrorCode; +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.context.ApplicationContext; + +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_NAMESPACE; +import static com.tencent.cloud.common.metadata.MetadataContext.LOCAL_SERVICE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * PolarisShortestResponseTimeBalancerTest. + * + * @author Yuwei Fu + */ +@ExtendWith(MockitoExtension.class) +public class PolarisShortestResponseTimeLoadBalancerTest { + + private static MockedStatic mockedApplicationContextAwareUtils; + private static Instance testInstance; + @Mock + private RouterAPI routerAPI; + @Mock + private ObjectProvider supplierObjectProvider; + + @BeforeAll + static void beforeAll() { + mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); + mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString())) + .thenReturn("unit-test"); + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetadataLocalProperties metadataLocalProperties = mock(MetadataLocalProperties.class); + StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class); + doReturn(metadataLocalProperties).when(applicationContext).getBean(MetadataLocalProperties.class); + doReturn(staticMetadataManager).when(applicationContext).getBean(StaticMetadataManager.class); + mockedApplicationContextAwareUtils.when(ApplicationContextAwareUtils::getApplicationContext) + .thenReturn(applicationContext); + testInstance = Instance.createDefaultInstance("instance-id", LOCAL_NAMESPACE, + LOCAL_SERVICE, "host", 8090); + } + + @AfterAll + static void afterAll() { + mockedApplicationContextAwareUtils.close(); + } + + @Test + public void chooseNormalLogicTest_thenReturnAvailablePolarisInstance() { + Request request = Mockito.mock(Request.class); + List mockInstanceList = new ArrayList<>(); + mockInstanceList.add(new PolarisServiceInstance(testInstance)); + + ServiceInstanceListSupplier serviceInstanceListSupplier = Mockito.mock(ServiceInstanceListSupplier.class); + when(serviceInstanceListSupplier.get(request)).thenReturn(Flux.just(mockInstanceList)); + + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(serviceInstanceListSupplier); + + ProcessLoadBalanceResponse mockLbRes = new ProcessLoadBalanceResponse(testInstance); + when(routerAPI.processLoadBalance(any())).thenReturn(mockLbRes); + + // request construct and execute invoke + PolarisShortestResponseTimeLoadBalancer PolarisShortestResponseTimeLoadBalancer = new PolarisShortestResponseTimeLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = PolarisShortestResponseTimeLoadBalancer.choose(request); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + // verify method has invoked + verify(supplierObjectProvider).getIfAvailable(any()); + + //result assert + Assertions.assertThat(serviceInstance).isNotNull(); + Assertions.assertThat(serviceInstance instanceof PolarisServiceInstance).isTrue(); + + PolarisServiceInstance polarisServiceInstance = (PolarisServiceInstance) serviceInstance; + + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getId()).isEqualTo("instance-id"); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getNamespace()).isEqualTo(LOCAL_NAMESPACE); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getService()).isEqualTo(LOCAL_SERVICE); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getHost()).isEqualTo("host"); + Assertions.assertThat(polarisServiceInstance.getPolarisInstance().getPort()).isEqualTo(8090); + } + + @Test + public void chooseExceptionTest_thenReturnEmptyInstance() { + + Request request = Mockito.mock(Request.class); + List mockInstanceList = new ArrayList<>(); + mockInstanceList.add(new PolarisServiceInstance(testInstance)); + + ServiceInstanceListSupplier serviceInstanceListSupplier = Mockito.mock(ServiceInstanceListSupplier.class); + when(serviceInstanceListSupplier.get(request)).thenReturn(Flux.just(mockInstanceList)); + + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(serviceInstanceListSupplier); + + when(routerAPI.processLoadBalance(any())).thenThrow(new PolarisException(ErrorCode.API_TIMEOUT)); + + // request construct and execute invoke + PolarisShortestResponseTimeLoadBalancer polarisShortestResponseTimeLoadBalancer = new PolarisShortestResponseTimeLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = polarisShortestResponseTimeLoadBalancer.choose(request); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + // verify method has invoked + verify(supplierObjectProvider).getIfAvailable(any()); + + //result assert + Assertions.assertThat(serviceInstance).isNull(); + } + + @Test + public void chooseEmptySupplierTest_thenReturnEmptyInstance() { + ServiceInstanceListSupplier noopSupplier = new NoopServiceInstanceListSupplier(); + when(supplierObjectProvider.getIfAvailable(any())).thenReturn(noopSupplier); + + // request construct and execute invoke + PolarisShortestResponseTimeLoadBalancer polarisShortestResponseTimeLoadBalancer = new PolarisShortestResponseTimeLoadBalancer(LOCAL_SERVICE, supplierObjectProvider, routerAPI); + Mono> responseMono = polarisShortestResponseTimeLoadBalancer.choose(); + ServiceInstance serviceInstance = responseMono.block().getServer(); + + //result assert + Assertions.assertThat(serviceInstance).isNull(); + } +} diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisWeightedRandomLoadBalancerAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisWeightedRandomLoadBalancerAutoConfigurationTest.java index 531e24d6b..13c695b3a 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisWeightedRandomLoadBalancerAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/loadbalancer/PolarisWeightedRandomLoadBalancerAutoConfigurationTest.java @@ -113,6 +113,17 @@ public class PolarisWeightedRandomLoadBalancerAutoConfigurationTest { }); } + @Test + public void testPolarisShortestResponseTimeInitialization() { + this.contextRunner + .withPropertyValues("spring.cloud.polaris.loadbalancer.strategy=shortestResponseTime").run(context -> { + assertThat(context).hasSingleBean(RestTemplate.class); + assertThatThrownBy(() -> { + context.getBean(RestTemplate.class).getForEntity("http://wrong.url", String.class); + }).isInstanceOf(Exception.class); + }); + } + @Configuration @EnableAutoConfiguration static class PolarisRibbonTest { diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java index 9c7f9fda4..311d0d2be 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java @@ -187,6 +187,11 @@ public class OrderConstant { */ public static Integer STAT_REPORTER_ORDER = 1; + /** + * Load Balancer config modifier order. + */ + public static Integer LOAD_BALANCER_CONFIG_ORDER = 2; + /** * Order of lossless configuration modifier. */ diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java index cb29597f3..946b3717d 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java @@ -133,6 +133,8 @@ public class EnhancedRestTemplateWrapInterceptor { catch (IOException e) { enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis); enhancedPluginContext.setThrowable(e); + enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get() + .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), httpRequest.getURI()); // Run exception enhanced plugins. pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext); throw e;