Merge pull request #1 from Tencent/main

mcs
pull/193/head
andrew shan 3 years ago committed by GitHub
commit cfd0ec1ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,7 @@
# Change Log # Change Log
--- ---
- [Feature: Support custom rate limit reject response info](https://github.com/Tencent/spring-cloud-tencent/pull/128)
- [Feature: Optimize config server address](https://github.com/Tencent/spring-cloud-tencent/pull/130) - [Feature: Support parse ratelimit rule expression labels.](https://github.com/Tencent/spring-cloud-tencent/pull/183)
- [Feature: Remove spring-javaformat-maven-plugin](https://github.com/Tencent/spring-cloud-tencent/pull/131) - [Feature: Router support request label.](https://github.com/Tencent/spring-cloud-tencent/pull/165)
- [feat:refactor loadbalancer module as a basic module for router and circuit breaker.](https://github.com/Tencent/spring-cloud-tencent/pull/136) - [Feature: Support router expression label](https://github.com/Tencent/spring-cloud-tencent/pull/190)

@ -12,7 +12,7 @@
## 介绍 ## 介绍
Spring Cloud Tencent 是腾讯开发和维护的一站式微服务解决方案。 Spring Cloud Tencent 是腾讯开的一站式微服务解决方案。
Spring Cloud Tencent 实现了Spring Cloud 标准微服务 SPI开发者可以基于 Spring Cloud Tencent 快速开发 Spring Cloud 云原生分布式应用。 Spring Cloud Tencent 实现了Spring Cloud 标准微服务 SPI开发者可以基于 Spring Cloud Tencent 快速开发 Spring Cloud 云原生分布式应用。
@ -80,6 +80,13 @@ Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要
- [项目概览](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88) - [项目概览](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [参与共建](https://github.com/Tencent/spring-cloud-tencent/wiki/Contributing) - [参与共建](https://github.com/Tencent/spring-cloud-tencent/wiki/Contributing)
## 交流群
扫描下面的二维码加入 Spring Cloud Tencent 交流群。
<img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
## License ## License
The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE)

@ -10,7 +10,7 @@ English | [简体中文](./README-zh.md)
## Introduction ## Introduction
Spring Cloud Tencent is a one-stop microservice solution developed and maintained by Tencent. Spring Cloud Tencent is a open source one-stop microservice solution from Tencent.
Spring Cloud Tencent implements the Spring Cloud standard microservice SPI, so developers can quickly develop Spring Cloud cloud-native distributed applications based on Spring Cloud Tencent. Spring Cloud Tencent implements the Spring Cloud standard microservice SPI, so developers can quickly develop Spring Cloud cloud-native distributed applications based on Spring Cloud Tencent.
@ -78,6 +78,12 @@ For example:
- [Project Structure Overview](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88) - [Project Structure Overview](https://github.com/Tencent/spring-cloud-tencent/wiki/%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88)
- [Participate in co-construction](https://github.com/Tencent/spring-cloud-tencent/wiki/Contributing) - [Participate in co-construction](https://github.com/Tencent/spring-cloud-tencent/wiki/Contributing)
## Chat Group
Please scan the QR code to join the chat group.
<img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
## License ## License
The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE)

@ -0,0 +1,8 @@
# Change Log
---
- [Feature: Support custom rate limit reject response info](https://github.com/Tencent/spring-cloud-tencent/pull/128)
- [Feature: Optimize config server address](https://github.com/Tencent/spring-cloud-tencent/pull/130)
- [Feature: Remove spring-javaformat-maven-plugin](https://github.com/Tencent/spring-cloud-tencent/pull/131)
- [feat:refactor loadbalancer module as a basic module for router and circuit breaker.](https://github.com/Tencent/spring-cloud-tencent/pull/136)
- [feat:enable distribute rate limit](https://github.com/Tencent/spring-cloud-tencent/pull/139)

@ -0,0 +1,5 @@
# Change Log
---
- [fix:fix routes of gateway doesn't refresh bug.](https://github.com/Tencent/spring-cloud-tencent/pull/158)
- [fix:Turn off automatic injection of Polars rule.](https://github.com/Tencent/spring-cloud-tencent/pull/162)

@ -0,0 +1,5 @@
# Change Log
---
- [fix:fix wrong context data storage.](https://github.com/Tencent/spring-cloud-tencent/pull/170)
- [fix:fix route not refreshing bug when first instance of one service up.](https://github.com/Tencent/spring-cloud-tencent/pull/174)

@ -0,0 +1,4 @@
# Change Log
---
- [fix:fix wrong isAliveFlag config when creating new PolarisServer.](https://github.com/Tencent/spring-cloud-tencent/pull/179)

@ -86,7 +86,7 @@
<properties> <properties>
<!-- Project revision --> <!-- Project revision -->
<revision>1.4.0-Hoxton.SR9-SNAPSHOT</revision> <revision>1.5.0-Hoxton.SR9-SNAPSHOT</revision>
<!-- Spring Cloud --> <!-- Spring Cloud -->
<spring.cloud.version>Hoxton.SR9</spring.cloud.version> <spring.cloud.version>Hoxton.SR9</spring.cloud.version>

@ -75,7 +75,7 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
Map<String, String> upstreamCustomMetadataMap = JacksonUtils Map<String, String> upstreamCustomMetadataMap = JacksonUtils
.deserialize2Map(customMetadataStr); .deserialize2Map(customMetadataStr);
MetadataContextHolder.init(upstreamCustomMetadataMap, null); MetadataContextHolder.init(upstreamCustomMetadataMap);
// Save to ServerWebExchange. // Save to ServerWebExchange.
serverWebExchange.getAttributes().put( serverWebExchange.getAttributes().put(

@ -72,7 +72,7 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
.deserialize2Map(customMetadataStr); .deserialize2Map(customMetadataStr);
try { try {
MetadataContextHolder.init(upstreamCustomMetadataMap, null); MetadataContextHolder.init(upstreamCustomMetadataMap);
filterChain.doFilter(httpServletRequest, httpServletResponse); filterChain.doFilter(httpServletRequest, httpServletResponse);
} }

@ -65,14 +65,13 @@ public class EncodeTransferMedataFeignInterceptor implements RequestInterceptor,
Map<String, String> headerMetadataMap = JacksonUtils Map<String, String> headerMetadataMap = JacksonUtils
.deserialize2Map(headerMetadataStr); .deserialize2Map(headerMetadataStr);
for (String key : headerMetadataMap.keySet()) { for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, metadataContext.putContext(MetadataContext.FRAGMENT_TRANSITIVE, key, headerMetadataMap.get(key));
headerMetadataMap.get(key));
} }
} }
} }
Map<String, String> customMetadata = metadataContext Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata); String metadataStr = JacksonUtils.serialize2Json(customMetadata);
requestTemplate.removeHeader(CUSTOM_METADATA); requestTemplate.removeHeader(CUSTOM_METADATA);

@ -63,12 +63,10 @@ public class EncodeTransferMedataRestTemplateInterceptor
Map<String, String> headerMetadataMap = JacksonUtils Map<String, String> headerMetadataMap = JacksonUtils
.deserialize2Map(metadataStr); .deserialize2Map(metadataStr);
for (String key : headerMetadataMap.keySet()) { for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, metadataContext.putContext(MetadataContext.FRAGMENT_TRANSITIVE, key, headerMetadataMap.get(key));
headerMetadataMap.get(key));
} }
} }
Map<String, String> customMetadata = metadataContext Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
metadataStr = JacksonUtils.serialize2Json(customMetadata); metadataStr = JacksonUtils.serialize2Json(customMetadata);
try { try {

@ -65,8 +65,7 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
if (metadataContext == null) { if (metadataContext == null) {
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
} }
Map<String, String> customMetadata = metadataContext Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata); String metadataStr = JacksonUtils.serialize2Json(customMetadata);
try { try {

@ -65,8 +65,7 @@ public class EncodeTransferMetadataZuulFilter extends ZuulFilter {
MetadataContext metadataContext = MetadataContextHolder.get(); MetadataContext metadataContext = MetadataContextHolder.get();
// add new metadata and cover old // add new metadata and cover old
Map<String, String> customMetadata = metadataContext Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) { if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata); String metadataStr = JacksonUtils.serialize2Json(customMetadata);
try { try {

@ -29,7 +29,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
/** /**
* Test for {@link MetadataTransferAutoConfiguration} * Test for {@link MetadataTransferAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -22,9 +22,9 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
@ -46,15 +46,15 @@ import org.springframework.web.bind.annotation.RestController;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
/** /**
* Test for {@link EncodeTransferMedataFeignInterceptor} * Test for {@link EncodeTransferMedataFeignInterceptor}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = DEFINED_PORT, @SpringBootTest(webEnvironment = DEFINED_PORT,
classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class, classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class,
properties = { "server.port=8081", properties = {"server.port=8081",
"spring.config.location = classpath:application-test.yml" }) "spring.config.location = classpath:application-test.yml"})
public class EncodeTransferMedataFeignInterceptorTest { public class EncodeTransferMedataFeignInterceptorTest {
@Autowired @Autowired
@ -67,19 +67,19 @@ public class EncodeTransferMedataFeignInterceptorTest {
public void test1() { public void test1() {
String metadata = testFeign.test(); String metadata = testFeign.test();
Assertions.assertThat(metadata) Assertions.assertThat(metadata)
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}{}"); .isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a")) Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1"); .isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")) Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2"); .isEqualTo("2");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "a"))
.isEqualTo("11"); .isEqualTo("11");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"))
.isEqualTo("22"); .isEqualTo("22");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "c"))
.isEqualTo("33"); .isEqualTo("33");
} }
@ -92,17 +92,15 @@ public class EncodeTransferMedataFeignInterceptorTest {
public String test( public String test(
@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) @RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
String systemMetadataStr = JacksonUtils return URLDecoder.decode(customMetadataStr, "UTF-8");
.serialize2Json(MetadataContextHolder.get().getAllSystemMetadata());
return URLDecoder.decode(customMetadataStr, "UTF-8") + systemMetadataStr;
} }
@FeignClient(name = "test-feign", url = "http://localhost:8081") @FeignClient(name = "test-feign", url = "http://localhost:8081")
public interface TestFeign { public interface TestFeign {
@RequestMapping(value = "/test", @RequestMapping(value = "/test",
headers = { MetadataConstant.HeaderName.CUSTOM_METADATA headers = {MetadataConstant.HeaderName.CUSTOM_METADATA
+ "={\"a\":\"11" + "\",\"b\":\"22\",\"c\":\"33\"}" }) + "={\"a\":\"11" + "\",\"b\":\"22\",\"c\":\"33\"}"})
String test(); String test();
} }

@ -22,9 +22,9 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
@ -47,7 +47,7 @@ import org.springframework.web.client.RestTemplate;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/** /**
* Test for {@link EncodeTransferMedataRestTemplateInterceptor} * Test for {@link EncodeTransferMedataRestTemplateInterceptor}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -77,19 +77,19 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
httpEntity, String.class) httpEntity, String.class)
.getBody(); .getBody();
Assertions.assertThat(metadata) Assertions.assertThat(metadata)
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}{}"); .isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a")) Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1"); .isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")) Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2"); .isEqualTo("2");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "a"))
.isEqualTo("11"); .isEqualTo("11");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"))
.isEqualTo("22"); .isEqualTo("22");
Assertions Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c")) .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "c"))
.isEqualTo("33"); .isEqualTo("33");
} }
@ -106,9 +106,7 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
public String test( public String test(
@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) @RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
String systemMetadataStr = JacksonUtils return URLDecoder.decode(customMetadataStr, "UTF-8");
.serialize2Json(MetadataContextHolder.get().getAllSystemMetadata());
return URLDecoder.decode(customMetadataStr, "UTF-8") + systemMetadataStr;
} }
} }

@ -13,6 +13,7 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.circuitbreaker.feign; package com.tencent.cloud.polaris.circuitbreaker.feign;
@ -20,9 +21,7 @@ package com.tencent.cloud.polaris.circuitbreaker.feign;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import com.tencent.cloud.common.constant.MetadataConstant.SystemMetadataKey;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.RetStatus;
import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.pojo.ServiceKey;
@ -32,6 +31,8 @@ import feign.Request;
import feign.Request.Options; import feign.Request.Options;
import feign.Response; import feign.Response;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static feign.Util.checkNotNull; import static feign.Util.checkNotNull;
@ -42,6 +43,9 @@ import static feign.Util.checkNotNull;
*/ */
public class PolarisFeignClient implements Client { public class PolarisFeignClient implements Client {
private static final Logger LOG = LoggerFactory.getLogger(PolarisFeignClient.class);
private final Client delegate; private final Client delegate;
private final ConsumerAPI consumerAPI; private final ConsumerAPI consumerAPI;
@ -64,6 +68,7 @@ public class PolarisFeignClient implements Client {
} }
catch (IOException origin) { catch (IOException origin) {
resultRequest.setRetStatus(RetStatus.RetFail); resultRequest.setRetStatus(RetStatus.RetFail);
throw origin; throw origin;
} }
finally { finally {
@ -74,24 +79,17 @@ public class PolarisFeignClient implements Client {
private ServiceCallResult createServiceCallResult(final Request request) { private ServiceCallResult createServiceCallResult(final Request request) {
ServiceCallResult resultRequest = new ServiceCallResult(); ServiceCallResult resultRequest = new ServiceCallResult();
MetadataContext metadataContext = MetadataContextHolder.get(); resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE);
String namespace = metadataContext String serviceName = request.requestTemplate().feignTarget().name();
.getSystemMetadata(SystemMetadataKey.PEER_NAMESPACE);
resultRequest.setNamespace(namespace);
String serviceName = metadataContext
.getSystemMetadata(SystemMetadataKey.PEER_SERVICE);
resultRequest.setService(serviceName); resultRequest.setService(serviceName);
String method = metadataContext.getSystemMetadata(SystemMetadataKey.PEER_PATH); URI uri = URI.create(request.url());
resultRequest.setMethod(method); resultRequest.setMethod(uri.getPath());
resultRequest.setRetStatus(RetStatus.RetSuccess); resultRequest.setRetStatus(RetStatus.RetSuccess);
String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; String sourceNamespace = MetadataContext.LOCAL_NAMESPACE;
String sourceService = MetadataContext.LOCAL_SERVICE; String sourceService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(sourceNamespace) if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) {
&& StringUtils.isNotBlank(sourceService)) { resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService));
resultRequest
.setCallerService(new ServiceKey(sourceNamespace, sourceService));
} }
URI uri = URI.create(request.url());
resultRequest.setHost(uri.getHost()); resultRequest.setHost(uri.getHost());
resultRequest.setPort(uri.getPort()); resultRequest.setPort(uri.getPort());

@ -43,6 +43,8 @@ public class TestPolarisFeignApp {
/** /**
* Get info of service B. * Get info of service B.
*
* @return info
*/ */
@GetMapping("/example/service/b/info") @GetMapping("/example/service/b/info")
String info(); String info();

@ -24,6 +24,8 @@ import com.tencent.polaris.api.config.consumer.ServiceRouterConfig;
import com.tencent.polaris.factory.config.ConfigurationImpl; import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig; import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig;
import org.springframework.beans.factory.annotation.Autowired;
/** /**
* Spring Cloud Tencent config Override polaris config. * Spring Cloud Tencent config Override polaris config.
* *
@ -31,6 +33,9 @@ import com.tencent.polaris.plugins.router.healthy.RecoverRouterConfig;
*/ */
public class DiscoveryConfigModifier implements PolarisConfigModifier { public class DiscoveryConfigModifier implements PolarisConfigModifier {
@Autowired
private PolarisDiscoveryProperties polarisDiscoveryProperties;
@Override @Override
public void modify(ConfigurationImpl configuration) { public void modify(ConfigurationImpl configuration) {
// Set excludeCircuitBreakInstances to false // Set excludeCircuitBreakInstances to false
@ -41,6 +46,10 @@ public class DiscoveryConfigModifier implements PolarisConfigModifier {
// Update modified config to source properties // Update modified config to source properties
configuration.getConsumer().getServiceRouter() configuration.getConsumer().getServiceRouter()
.setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig); .setPluginConfig(ServiceRouterConfig.DEFAULT_ROUTER_RECOVER, recoverRouterConfig);
// Set ServiceRefreshInterval
configuration.getConsumer().getLocalCache()
.setServiceListRefreshInterval(polarisDiscoveryProperties.getServiceListRefreshInterval());
} }
@Override @Override

@ -105,6 +105,11 @@ public class PolarisDiscoveryProperties {
@Value("${spring.cloud.polaris.discovery.health-check-url:}") @Value("${spring.cloud.polaris.discovery.health-check-url:}")
private String healthCheckUrl; private String healthCheckUrl;
/**
* Millis interval of refresh of service info list. Default: 60000.
*/
private Long serviceListRefreshInterval = 60000L;
@Autowired @Autowired
private Environment environment; private Environment environment;
@ -218,6 +223,14 @@ public class PolarisDiscoveryProperties {
this.healthCheckUrl = healthCheckUrl; this.healthCheckUrl = healthCheckUrl;
} }
public Long getServiceListRefreshInterval() {
return serviceListRefreshInterval;
}
public void setServiceListRefreshInterval(Long serviceListRefreshInterval) {
this.serviceListRefreshInterval = serviceListRefreshInterval;
}
@Override @Override
public String toString() { public String toString() {
return "PolarisProperties{" + "token='" + token + '\'' + ", namespace='" return "PolarisProperties{" + "token='" + token + '\'' + ", namespace='"

@ -19,6 +19,7 @@
package com.tencent.cloud.polaris.discovery; package com.tencent.cloud.polaris.discovery;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClientConfiguration; import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClientConfiguration;
import com.tencent.cloud.polaris.discovery.refresh.PolarisRefreshConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -32,8 +33,8 @@ import org.springframework.context.annotation.Import;
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisDiscoveryEnabled @ConditionalOnPolarisDiscoveryEnabled
@Import({ PolarisDiscoveryClientConfiguration.class, @Import({PolarisDiscoveryClientConfiguration.class,
PolarisReactiveDiscoveryClientConfiguration.class }) PolarisReactiveDiscoveryClientConfiguration.class, PolarisRefreshConfiguration.class})
public class PolarisDiscoveryAutoConfiguration { public class PolarisDiscoveryAutoConfiguration {
@Bean @Bean

@ -20,7 +20,6 @@ package com.tencent.cloud.polaris.discovery;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant.SystemMetadataKey;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
@ -33,6 +32,7 @@ import com.tencent.polaris.api.rpc.GetInstancesRequest;
import com.tencent.polaris.api.rpc.GetServicesRequest; import com.tencent.polaris.api.rpc.GetServicesRequest;
import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.api.rpc.InstancesResponse;
import com.tencent.polaris.api.rpc.ServicesResponse; import com.tencent.polaris.api.rpc.ServicesResponse;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -52,6 +52,9 @@ public class PolarisDiscoveryHandler {
@Autowired @Autowired
private ProviderAPI providerAPI; private ProviderAPI providerAPI;
@Autowired
private SDKContext sdkContext;
@Autowired @Autowired
private ConsumerAPI polarisConsumer; private ConsumerAPI polarisConsumer;
@ -66,13 +69,10 @@ public class PolarisDiscoveryHandler {
GetInstancesRequest getInstancesRequest = new GetInstancesRequest(); GetInstancesRequest getInstancesRequest = new GetInstancesRequest();
getInstancesRequest.setNamespace(namespace); getInstancesRequest.setNamespace(namespace);
getInstancesRequest.setService(service); getInstancesRequest.setService(service);
String method = MetadataContextHolder.get()
.getSystemMetadata(SystemMetadataKey.PEER_PATH);
getInstancesRequest.setMethod(method);
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> allTransitiveCustomMetadata = MetadataContextHolder.get() Map<String, String> allTransitiveCustomMetadata = MetadataContextHolder.get().
.getAllTransitiveCustomMetadata(); getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (StringUtils.isNotBlank(localNamespace) || StringUtils.isNotBlank(localService) if (StringUtils.isNotBlank(localNamespace) || StringUtils.isNotBlank(localService)
|| null != allTransitiveCustomMetadata) { || null != allTransitiveCustomMetadata) {
ServiceInfo sourceService = new ServiceInfo(); ServiceInfo sourceService = new ServiceInfo();
@ -114,6 +114,10 @@ public class PolarisDiscoveryHandler {
return providerAPI; return providerAPI;
} }
public SDKContext getSdkContext() {
return sdkContext;
}
/** /**
* Return all service for given namespace. * Return all service for given namespace.
* @return service list * @return service list

@ -0,0 +1,86 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.discovery.refresh;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import static com.tencent.cloud.polaris.discovery.refresh.PolarisServiceStatusChangeListener.INDEX;
/**
* Begin refresh when application is ready.
*
* @author Haotian Zhang
*/
public class PolarisRefreshApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent>, ApplicationEventPublisherAware {
private static final Logger LOG = LoggerFactory.getLogger(PolarisRefreshConfiguration.class);
private static final int DELAY = 60;
private final PolarisDiscoveryHandler polarisDiscoveryHandler;
private final PolarisServiceStatusChangeListener polarisServiceStatusChangeListener;
private final ScheduledExecutorService refreshExecutor;
private ApplicationEventPublisher publisher;
public PolarisRefreshApplicationReadyEventListener(PolarisDiscoveryHandler polarisDiscoveryHandler, PolarisServiceStatusChangeListener polarisServiceStatusChangeListener) {
this.polarisDiscoveryHandler = polarisDiscoveryHandler;
this.polarisServiceStatusChangeListener = polarisServiceStatusChangeListener;
this.refreshExecutor = Executors.newSingleThreadScheduledExecutor(
new NamedThreadFactory("polaris-service-refresh"));
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// Register service change listener.
polarisDiscoveryHandler.getSdkContext().getExtensions().getLocalRegistry()
.registerResourceListener(polarisServiceStatusChangeListener);
// Begin scheduled refresh thread.
refresh();
}
/**
* Start the refresh thread.
*/
public void refresh() {
refreshExecutor.scheduleWithFixedDelay(() -> {
try {
// Trigger reload of gateway route cache.
this.publisher.publishEvent(new HeartbeatEvent(this, INDEX.getAndIncrement()));
}
catch (Exception e) {
LOG.error("refresh polaris service error.", e);
}
}, DELAY, DELAY, TimeUnit.SECONDS);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}

@ -0,0 +1,48 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.discovery.refresh;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for listening the change of service status.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
public class PolarisRefreshConfiguration {
@Bean
@ConditionalOnMissingBean
public PolarisServiceStatusChangeListener polarisServiceChangeListener() {
return new PolarisServiceStatusChangeListener();
}
@Bean
@ConditionalOnMissingBean
public PolarisRefreshApplicationReadyEventListener polarisServiceStatusApplicationReadyEventListener(
PolarisDiscoveryHandler polarisDiscoveryHandler,
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener) {
return new PolarisRefreshApplicationReadyEventListener(polarisDiscoveryHandler, polarisServiceStatusChangeListener);
}
}

@ -0,0 +1,96 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.discovery.refresh;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import com.google.common.collect.Sets;
import com.tencent.polaris.api.plugin.registry.AbstractResourceEventListener;
import com.tencent.polaris.api.pojo.RegistryCacheValue;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.client.pojo.ServiceInstancesByProto;
import com.tencent.polaris.client.pojo.ServicesByProto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.CollectionUtils;
/**
* Change listener of Polaris service info. When service info is created or deleted, or, instance of service is from 0 to
*
* @author Haotian Zhang
*/
public class PolarisServiceStatusChangeListener extends AbstractResourceEventListener implements ApplicationEventPublisherAware {
/**
* Index of service info status.
*/
public static final AtomicLong INDEX = new AtomicLong(0);
private static final Logger LOG = LoggerFactory.getLogger(PolarisServiceStatusChangeListener.class);
private ApplicationEventPublisher publisher;
@Override
public void onResourceUpdated(ServiceEventKey svcEventKey, RegistryCacheValue oldValue,
RegistryCacheValue newValue) {
if (newValue.getEventType() == ServiceEventKey.EventType.SERVICE) {
if (oldValue instanceof ServicesByProto && newValue instanceof ServicesByProto) {
LOG.debug("receive service={} change event", svcEventKey);
Set<String> oldServiceInfoSet = ((ServicesByProto) oldValue).getServices().stream()
.map(i -> i.getNamespace() + "::" + i.getService()).collect(Collectors.toSet());
Set<String> newServiceInfoSet = ((ServicesByProto) newValue).getServices().stream()
.map(i -> i.getNamespace() + "::" + i.getService()).collect(Collectors.toSet());
Sets.SetView<String> addServiceInfoSetView = Sets.difference(newServiceInfoSet, oldServiceInfoSet);
Sets.SetView<String> deleteServiceInfoSetView = Sets.difference(oldServiceInfoSet, newServiceInfoSet);
if (addServiceInfoSetView.isEmpty() && deleteServiceInfoSetView.isEmpty()) {
return;
}
LOG.info("Service status is update. Add service of {}. Delete service of {}", addServiceInfoSetView, deleteServiceInfoSetView);
// Trigger reload of gateway route cache.
this.publisher.publishEvent(new HeartbeatEvent(this, INDEX.getAndIncrement()));
}
}
else if (newValue.getEventType() == ServiceEventKey.EventType.INSTANCE) {
if (oldValue instanceof ServiceInstancesByProto && newValue instanceof ServiceInstancesByProto) {
LOG.debug("receive service instances={} change event", svcEventKey);
ServiceInstancesByProto oldIns = (ServiceInstancesByProto) oldValue;
ServiceInstancesByProto newIns = (ServiceInstancesByProto) newValue;
if ((CollectionUtils.isEmpty(oldIns.getInstances()) && !CollectionUtils.isEmpty(newIns.getInstances())) ||
(!CollectionUtils.isEmpty(oldIns.getInstances()) && CollectionUtils.isEmpty(newIns.getInstances()))) {
LOG.info("Service status of {} is update.", newIns.getService());
// Trigger reload of gateway route cache.
this.publisher.publishEvent(new HeartbeatEvent(this, INDEX.getAndIncrement()));
}
}
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}

@ -19,9 +19,9 @@
package com.tencent.cloud.polaris.registry; package com.tencent.cloud.polaris.registry;
import java.net.URI; import java.net.URI;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration; import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.client.api.SDKContext;
@ -84,7 +84,7 @@ public class PolarisRegistration implements Registration, ServiceInstance {
@Override @Override
public Map<String, String> getMetadata() { public Map<String, String> getMetadata() {
return MetadataContextHolder.get().getAllSystemMetadata(); return Collections.emptyMap();
} }
public PolarisDiscoveryProperties getPolarisProperties() { public PolarisDiscoveryProperties getPolarisProperties() {

@ -34,14 +34,13 @@ import com.tencent.polaris.api.rpc.InstanceHeartbeatRequest;
import com.tencent.polaris.api.rpc.InstanceRegisterRequest; import com.tencent.polaris.api.rpc.InstanceRegisterRequest;
import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.api.rpc.InstancesResponse;
import com.tencent.polaris.client.util.NamedThreadFactory; import com.tencent.polaris.client.util.NamedThreadFactory;
import org.apache.logging.log4j.util.Strings; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.cloud.client.serviceregistry.Registration; import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry; import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.util.StringUtils;
import static org.springframework.util.ReflectionUtils.rethrowRuntimeException; import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;
@ -199,7 +198,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
// first. // first.
// If the health check passes, the heartbeat will be reported. // If the health check passes, the heartbeat will be reported.
// If it does not pass, the heartbeat will not be reported. // If it does not pass, the heartbeat will not be reported.
if (Strings.isNotEmpty(healthCheckEndpoint)) { if (StringUtils.isNotBlank(healthCheckEndpoint)) {
if (!healthCheckEndpoint.startsWith("/")) { if (!healthCheckEndpoint.startsWith("/")) {
healthCheckEndpoint = "/" + healthCheckEndpoint; healthCheckEndpoint = "/" + healthCheckEndpoint;
} }

@ -52,11 +52,9 @@ public class PolarisServiceRegistryAutoConfiguration {
@Bean @Bean
public PolarisServiceRegistry polarisServiceRegistry( public PolarisServiceRegistry polarisServiceRegistry(
PolarisDiscoveryProperties polarisDiscoveryProperties, PolarisDiscoveryProperties polarisDiscoveryProperties, PolarisDiscoveryHandler polarisDiscoveryHandler,
PolarisDiscoveryHandler polarisDiscoveryHandler,
MetadataLocalProperties metadataLocalProperties) { MetadataLocalProperties metadataLocalProperties) {
return new PolarisServiceRegistry(polarisDiscoveryProperties, return new PolarisServiceRegistry(polarisDiscoveryProperties, polarisDiscoveryHandler, metadataLocalProperties);
polarisDiscoveryHandler, metadataLocalProperties);
} }
@Bean @Bean
@ -77,5 +75,4 @@ public class PolarisServiceRegistryAutoConfiguration {
return new PolarisAutoServiceRegistration(registry, return new PolarisAutoServiceRegistration(registry,
autoServiceRegistrationProperties, registration); autoServiceRegistrationProperties, registration);
} }
} }

@ -59,6 +59,12 @@
"type": "java.lang.Integer", "type": "java.lang.Integer",
"defaultValue": 100, "defaultValue": 100,
"description": "the weight of polaris instance , use to load-balance." "description": "the weight of polaris instance , use to load-balance."
},
{
"name": "spring.cloud.polaris.discovery.service-list-refresh-interval",
"type": "java.lang.Long",
"defaultValue": 60000,
"description": "Millis interval of refresh of service info list. Default: 60000."
} }
] ]
} }

@ -26,7 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
* Test for {@link PolarisDiscoveryProperties} * Test for {@link PolarisDiscoveryProperties}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -38,7 +38,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisDiscoveryAutoConfiguration} * Test for {@link PolarisDiscoveryAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -34,7 +34,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisDiscoveryClientConfiguration} * Test for {@link PolarisDiscoveryClientConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisDiscoveryClient} * Test for {@link PolarisDiscoveryClient}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -41,7 +41,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisServiceDiscovery} * Test for {@link PolarisServiceDiscovery}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -35,7 +35,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisReactiveDiscoveryClientConfiguration} * Test for {@link PolarisReactiveDiscoveryClientConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -37,7 +37,7 @@ import static java.util.Collections.singletonList;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisReactiveDiscoveryClient} * Test for {@link PolarisReactiveDiscoveryClient}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -37,7 +37,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisServiceRegistryAutoConfiguration} * Test for {@link PolarisServiceRegistryAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -41,7 +41,7 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisServiceRegistry} * Test for {@link PolarisServiceRegistry}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -39,7 +39,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisRibbonServerListConfiguration} * Test for {@link PolarisRibbonServerListConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -46,7 +46,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Test for {@link PolarisServerList} * Test for {@link PolarisServerList}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -34,10 +34,6 @@
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId> <artifactId>router-nearby</artifactId>
</exclusion> </exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion> <exclusion>
<groupId>com.tencent.polaris</groupId> <groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId> <artifactId>router-canary</artifactId>

@ -0,0 +1,72 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.ratelimit;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;
import org.springframework.util.CollectionUtils;
/**
* resolve labels from rate limit rule.
*
*@author lepdou 2022-05-13
*/
public class RateLimitRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RateLimitRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String service) {
RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
if (rateLimitRule == null) {
return Collections.emptySet();
}
List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RateLimitProto.Rule rule : rules) {
Map<String, ModelProto.MatchString> labels = rule.getLabelsMap();
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptySet();
}
for (String key : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(key)) {
expressionLabels.add(key);
}
}
}
return expressionLabels;
}
}

@ -19,6 +19,8 @@
package com.tencent.cloud.polaris.ratelimit.config; package com.tencent.cloud.polaris.ratelimit.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter; import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
@ -62,6 +64,11 @@ public class RateLimitConfiguration {
return LimitAPIFactory.createLimitAPIByContext(polarisContext); return LimitAPIFactory.createLimitAPIByContext(polarisContext);
} }
@Bean
public RateLimitRuleLabelResolver rateLimitRuleLabelService(ServiceRuleManager serviceRuleManager) {
return new RateLimitRuleLabelResolver(serviceRuleManager);
}
/** /**
* Create when web application type is SERVLET. * Create when web application type is SERVLET.
*/ */
@ -73,9 +80,10 @@ public class RateLimitConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelServletResolver labelResolver, @Nullable PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckServletFilter(limitAPI, labelResolver, return new QuotaCheckServletFilter(limitAPI, labelResolver,
polarisRateLimitProperties); polarisRateLimitProperties, rateLimitRuleLabelResolver);
} }
@Bean @Bean
@ -101,9 +109,10 @@ public class RateLimitConfiguration {
@Bean @Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver, @Nullable PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckReactiveFilter(limitAPI, labelResolver, return new QuotaCheckReactiveFilter(limitAPI, labelResolver,
polarisRateLimitProperties); polarisRateLimitProperties, rateLimitRuleLabelResolver);
} }
} }

@ -21,10 +21,14 @@ package com.tencent.cloud.polaris.ratelimit.filter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.google.common.collect.Maps;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
@ -42,7 +46,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
@ -52,7 +55,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Reactive filter to check quota. * Reactive filter to check quota.
* *
* @author Haotian Zhang * @author Haotian Zhang, lepdou
*/ */
public class QuotaCheckReactiveFilter implements WebFilter, Ordered { public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
@ -65,14 +68,18 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final PolarisRateLimitProperties polarisRateLimitProperties; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips; private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelReactiveResolver labelResolver, PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
} }
@PostConstruct @PostConstruct
@ -90,27 +97,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> labels = new HashMap<>(); Map<String, String> labels = getRequestLabels(exchange, localNamespace, localService);
// add build in labels
String path = exchange.getRequest().getURI().getPath();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add custom labels
if (labelResolver != null) {
try {
Map<String, String> customLabels = labelResolver.resolve(exchange);
if (!CollectionUtils.isEmpty(customLabels)) {
labels.putAll(customLabels);
}
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e);
}
}
try { try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
@ -134,4 +121,42 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
return chain.filter(exchange); return chain.filter(exchange);
} }
private Map<String, String> getRequestLabels(ServerWebExchange exchange, String localNamespace, String localService) {
Map<String, String> labels = new HashMap<>();
// add build in labels
String path = exchange.getRequest().getURI().getPath();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add rule expression labels
Map<String, String> expressionLabels = getRuleExpressionLabels(exchange, localNamespace, localService);
labels.putAll(expressionLabels);
// add custom labels
Map<String, String> customResolvedLabels = getCustomResolvedLabels(exchange);
labels.putAll(customResolvedLabels);
return labels;
}
private Map<String, String> getCustomResolvedLabels(ServerWebExchange exchange) {
if (labelResolver != null) {
try {
return labelResolver.resolve(exchange);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e);
}
}
return Maps.newHashMap();
}
private Map<String, String> getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(exchange, expressionLabels);
}
} }

@ -19,8 +19,10 @@
package com.tencent.cloud.polaris.ratelimit.filter; package com.tencent.cloud.polaris.ratelimit.filter;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -29,6 +31,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
@ -42,7 +46,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD;
@ -50,13 +53,12 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/** /**
* Servlet filter to check quota. * Servlet filter to check quota.
* *
* @author Haotian Zhang * @author Haotian Zhang, lepdou
*/ */
@Order(RateLimitConstant.FILTER_ORDER) @Order(RateLimitConstant.FILTER_ORDER)
public class QuotaCheckServletFilter extends OncePerRequestFilter { public class QuotaCheckServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckServletFilter.class);
.getLogger(QuotaCheckServletFilter.class);
private final LimitAPI limitAPI; private final LimitAPI limitAPI;
@ -64,14 +66,18 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private final PolarisRateLimitProperties polarisRateLimitProperties; private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips; private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI, public QuotaCheckServletFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelServletResolver labelResolver, PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) { PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties; this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
} }
@PostConstruct @PostConstruct
@ -80,52 +86,68 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
} }
@Override @Override
protected void doFilterInternal(HttpServletRequest request, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
String localNamespace = MetadataContext.LOCAL_NAMESPACE; String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE; String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> labels = getRequestLabels(request, localNamespace, localService);
try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, null);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(rejectTips);
return;
}
filterChain.doFilter(request, response);
}
catch (Throwable t) {
// An exception occurs in the rate limiting API call,
// which should not affect the call of the business process.
LOG.error("fail to invoke getQuota, service is " + localService, t);
filterChain.doFilter(request, response);
}
}
private Map<String, String> getRequestLabels(HttpServletRequest request, String localNamespace, String localService) {
Map<String, String> labels = new HashMap<>(); Map<String, String> labels = new HashMap<>();
// add build in labels // add build in labels
String path = request.getRequestURI(); String path = request.getRequestURI();
if (StringUtils.isNotBlank(path)) { if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path); labels.put(LABEL_METHOD, path);
} }
// add custom labels // add rule expression labels
Map<String, String> expressionLabels = getRuleExpressionLabels(request, localNamespace, localService);
labels.putAll(expressionLabels);
// add custom resolved labels
Map<String, String> customLabels = getCustomResolvedLabels(request);
labels.putAll(customLabels);
return labels;
}
private Map<String, String> getCustomResolvedLabels(HttpServletRequest request) {
if (labelResolver != null) { if (labelResolver != null) {
try { try {
Map<String, String> customLabels = labelResolver.resolve(request); return labelResolver.resolve(request);
if (!CollectionUtils.isEmpty(customLabels)) {
labels.putAll(customLabels);
}
} }
catch (Throwable e) { catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e); labelResolver.getClass().getName(), e);
} }
} }
return Collections.emptyMap();
try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, null);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(rejectTips);
}
else {
filterChain.doFilter(request, response);
}
}
catch (Throwable t) {
// An exception occurs in the rate limiting API call,
// which should not affect the call of the business process.
LOG.error("fail to invoke getQuota, service is " + localService, t);
filterChain.doFilter(request, response);
}
} }
private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(request, expressionLabels);
}
} }

@ -27,6 +27,19 @@
<artifactId>router-rule</artifactId> <artifactId>router-rule</artifactId>
</dependency> </dependency>
<!-- Polaris dependencies end --> <!-- Polaris dependencies end -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -0,0 +1,184 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.BestAvailableRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RetryRule;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.WeightedResponseTimeRule;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils;
import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
*
* Service routing entrance.
*
* Rule routing needs to rely on request parameters for server filtering,
* and {@link com.netflix.loadbalancer.ServerListFilter#getFilteredListOfServers(List)}
* The interface cannot obtain the context object of the request granularity,
* so the routing capability cannot be achieved through ServerListFilter.
*
* And {@link com.netflix.loadbalancer.IRule#choose(Object)} provides the ability to pass in context parameters,
* so routing capabilities are implemented through IRule.
*
* @author Haotian Zhang, lepdou
*/
public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule {
private final static String STRATEGY_RANDOM = "random";
private final static String STRATEGY_ROUND_ROBIN = "roundRobin";
private final static String STRATEGY_WEIGHT = "polarisWeighted";
private final static String STRATEGY_RETRY = "retry";
private final static String STRATEGY_RESPONSE_TIME_WEIGHTED = "responseTimeWeighted";
private final static String STRATEGY_BEST_AVAILABLE = "bestAvailable";
private final static String STRATEGY_ZONE_AVOIDANCE = "zoneAvoidance";
private final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule";
private final PolarisLoadBalancerProperties loadBalancerProperties;
private final RouterAPI routerAPI;
private final AbstractLoadBalancerRule delegateRule;
public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties,
IClientConfig iClientConfig) {
this.routerAPI = routerAPI;
this.loadBalancerProperties = polarisLoadBalancerProperties;
delegateRule = getRule();
delegateRule.initWithNiwsConfig(iClientConfig);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
// 1. get all servers
List<Server> allServers = getLoadBalancer().getReachableServers();
if (CollectionUtils.isEmpty(allServers)) {
return null;
}
// 2. filter by router
List<Server> serversAfterRouter = doRouter(allServers, key);
// 3. filter by load balance.
// A LoadBalancer needs to be regenerated for each request,
// because the list of servers may be different after filtered by router
ILoadBalancer loadBalancer = new SimpleLoadBalancer();
loadBalancer.addServers(serversAfterRouter);
delegateRule.setLoadBalancer(loadBalancer);
return delegateRule.choose(key);
}
private List<Server> doRouter(List<Server> allServers, Object key) {
ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers);
// filter instance by routers
ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key);
ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest);
List<Server> filteredInstances = new ArrayList<>();
ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServer(serviceInstances, instance));
}
return filteredInstances;
}
private ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object key) {
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances);
Map<String, String> routerMetadata;
if (key instanceof PolarisRouterContext) {
routerMetadata = ((PolarisRouterContext) key).getLabels();
}
else {
routerMetadata = Collections.emptyMap();
}
String srcNamespace = MetadataContext.LOCAL_NAMESPACE;
String srcService = MetadataContext.LOCAL_SERVICE;
if (StringUtils.isNotBlank(srcNamespace) && StringUtils.isNotBlank(srcService)) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(srcNamespace);
serviceInfo.setService(srcService);
serviceInfo.setMetadata(routerMetadata);
processRoutersRequest.setSourceService(serviceInfo);
}
return processRoutersRequest;
}
public AbstractLoadBalancerRule getRule() {
String loadBalanceStrategy = loadBalancerProperties.getStrategy();
if (org.springframework.util.StringUtils.isEmpty(loadBalanceStrategy)) {
return new RoundRobinRule();
}
switch (loadBalanceStrategy) {
case STRATEGY_RANDOM:
return new RandomRule();
case STRATEGY_WEIGHT:
return new PolarisWeightedRule(routerAPI);
case STRATEGY_RETRY:
return new RetryRule();
case STRATEGY_RESPONSE_TIME_WEIGHTED:
return new WeightedResponseTimeRule();
case STRATEGY_BEST_AVAILABLE:
return new BestAvailableRule();
case STRATEGY_ROUND_ROBIN:
return new RoundRobinRule();
case STRATEGY_AVAILABILITY_FILTERING:
return new AvailabilityFilteringRule();
case STRATEGY_ZONE_AVOIDANCE:
default:
return new ZoneAvoidanceRule();
}
}
}

@ -0,0 +1,53 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.CollectionUtils;
/**
* the context for router.
*
*@author lepdou 2022-05-17
*/
public class PolarisRouterContext {
private Map<String, String> labels;
public Map<String, String> getLabels() {
if (CollectionUtils.isEmpty(labels)) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(labels);
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
public void putLabel(String key, String value) {
if (labels == null) {
labels = new HashMap<>();
}
labels.put(key, value);
}
}

@ -13,11 +13,20 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.router;
/** /**
* Package info of router. * Router constants.
* *
* @author Haotian Zhang *@author lepdou 2022-05-17
*/ */
package com.tencent.cloud.polaris.router; public class RouterConstants {
/**
* the header of router label.
*/
public static final String ROUTER_LABEL_HEADER = "router-label";
}

@ -0,0 +1,75 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RoutingProto;
import org.springframework.util.CollectionUtils;
/**
* Resolve label expressions from routing rules.
* @author lepdou 2022-05-19
*/
public class RouterRuleLabelResolver {
private final ServiceRuleManager serviceRuleManager;
public RouterRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
this.serviceRuleManager = serviceRuleManager;
}
public Set<String> getExpressionLabelKeys(String namespace, String sourceService, String dstService) {
List<RoutingProto.Route> rules = serviceRuleManager.getServiceRouterRule(namespace, sourceService, dstService);
if (CollectionUtils.isEmpty(rules)) {
return Collections.emptySet();
}
Set<String> expressionLabels = new HashSet<>();
for (RoutingProto.Route rule : rules) {
List<RoutingProto.Source> sources = rule.getSourcesList();
if (CollectionUtils.isEmpty(sources)) {
continue;
}
for (RoutingProto.Source source : sources) {
Map<String, ModelProto.MatchString> labels = source.getMetadataMap();
if (CollectionUtils.isEmpty(labels)) {
continue;
}
for (String labelKey : labels.keySet()) {
if (ExpressionLabelUtils.isExpressionLabel(labelKey)) {
expressionLabels.add(labelKey);
}
}
}
}
return expressionLabels;
}
}

@ -0,0 +1,63 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router;
import java.util.List;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
/**
* Simple load balancer only for getting and setting servers.
*
*@author lepdou 2022-05-17
*/
public class SimpleLoadBalancer implements ILoadBalancer {
private List<Server> servers;
@Override
public void addServers(List<Server> newServers) {
this.servers = newServers;
}
@Override
public Server chooseServer(Object key) {
return null;
}
@Override
public void markServerDown(Server server) {
}
@Override
public List<Server> getServerList(boolean availableOnly) {
return servers;
}
@Override
public List<Server> getReachableServers() {
return servers;
}
@Override
public List<Server> getAllServers() {
return servers;
}
}

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.tencent.cloud.polaris.router.feign.PolarisFeignLoadBalancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* configuration for feign component.
*
*@author lepdou 2022-05-16
*/
@Configuration
public class FeignConfiguration {
@Bean
@ConditionalOnMissingBean
public PolarisFeignLoadBalancer polarisFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector) {
return new PolarisFeignLoadBalancer(lb, clientConfig, serverIntrospector);
}
}

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IRule;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto configuration for ribbon components.
* @author lepdou 2022-05-17
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI,
PolarisLoadBalancerProperties polarisLoadBalancerProperties,
IClientConfig iClientConfig) {
return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig);
}
}

@ -0,0 +1,69 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.config;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.feign.PolarisCachingSpringLoadBalanceFactory;
import com.tencent.cloud.polaris.router.feign.RouterLabelInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.PolarisLoadBalancerBeanPostProcessor;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.lang.Nullable;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
/**
* router module auto configuration.
*
*@author lepdou 2022-05-11
*/
@Configuration
@RibbonClients(defaultConfiguration = {FeignConfiguration.class, RibbonConfiguration.class})
public class RouterAutoConfiguration {
@Bean
public RouterLabelInterceptor routerLabelInterceptor(@Nullable RouterLabelResolver resolver,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
return new RouterLabelInterceptor(resolver, metadataLocalProperties, routerRuleLabelResolver);
}
@Bean
public PolarisCachingSpringLoadBalanceFactory polarisCachingSpringLoadBalanceFactory(SpringClientFactory factory) {
return new PolarisCachingSpringLoadBalanceFactory(factory);
}
@Bean
@Order(HIGHEST_PRECEDENCE)
public PolarisLoadBalancerBeanPostProcessor polarisLoadBalancerBeanPostProcessor() {
return new PolarisLoadBalancerBeanPostProcessor();
}
@Bean
public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) {
return new RouterRuleLabelResolver(serviceRuleManager);
}
}

@ -0,0 +1,81 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.feign;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import feign.RequestTemplate;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
/**
* Resolve rule expression label from feign request.
* @author lepdou 2022-05-20
*/
public class FeignExpressionLabelUtils {
public static Map<String, String> resolve(RequestTemplate request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) {
String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) {
String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.method());
}
else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) {
labels.put(labelKey, request.request().url());
}
}
return labels;
}
public static String getHeaderValue(RequestTemplate request, String key) {
Map<String, Collection<String>> headers = request.headers();
return ExpressionLabelUtils.getFirstValue(headers, key);
}
public static String getQueryValue(RequestTemplate request, String key) {
return ExpressionLabelUtils.getFirstValue(request.queries(), key);
}
}

@ -0,0 +1,72 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.feign;
import java.util.Map;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Extends CachingSpringLoadBalancerFactory to be able to create PolarisFeignLoadBalance.
*
*@author lepdou 2022-05-16
*/
public class PolarisCachingSpringLoadBalanceFactory extends CachingSpringLoadBalancerFactory {
private final Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
public PolarisCachingSpringLoadBalanceFactory(SpringClientFactory factory) {
super(factory);
}
public PolarisCachingSpringLoadBalanceFactory(SpringClientFactory factory,
LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
super(factory, loadBalancedRetryPolicyFactory);
}
@Override
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer loadBalancer = new PolarisFeignLoadBalancer(lb, config, serverIntrospector);
//There is a concurrency problem here.
//When the concurrency is high, it may cause a service to create multiple FeignLoadBalancers.
//But there is no concurrency control in CachingSpringLoadBalancerFactory,
//so no locks will be added here for the time being
cache.putIfAbsent(clientName, loadBalancer);
return loadBalancer;
}
}

@ -0,0 +1,76 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.feign;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterConstants;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
import org.springframework.util.CollectionUtils;
/**
* In order to pass router context for {@link com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule}.
*
*@author lepdou 2022-05-16
*/
public class PolarisFeignLoadBalancer extends FeignLoadBalancer {
public PolarisFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {
super(lb, clientConfig, serverIntrospector);
}
@Override
protected void customizeLoadBalancerCommandBuilder(RibbonRequest request, IClientConfig config,
LoadBalancerCommand.Builder<RibbonResponse> builder) {
Map<String, Collection<String>> headers = request.getRequest().headers();
Collection<String> labelHeaderValues = headers.get(RouterConstants.ROUTER_LABEL_HEADER);
if (CollectionUtils.isEmpty(labelHeaderValues)) {
builder.withServerLocator(null);
return;
}
PolarisRouterContext routerContext = new PolarisRouterContext();
labelHeaderValues.forEach(labelHeaderValue -> {
Map<String, String> labels = JacksonUtils.deserialize2Map(labelHeaderValue);
if (!CollectionUtils.isEmpty(labels)) {
Map<String, String> unescapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.unescape(entry.getKey());
String escapedValue = ExpressionLabelUtils.unescape(entry.getValue());
unescapeLabels.put(escapedKey, escapedValue);
}
routerContext.setLabels(unescapeLabels);
}
});
builder.withServerLocator(routerContext);
}
}

@ -0,0 +1,123 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.feign;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.router.RouterConstants;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
/**
* Resolver labels from request.
*
*@author lepdou 2022-05-12
*/
public class RouterLabelInterceptor implements RequestInterceptor, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelInterceptor.class);
private final RouterLabelResolver resolver;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
public RouterLabelInterceptor(RouterLabelResolver resolver,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
this.resolver = resolver;
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void apply(RequestTemplate requestTemplate) {
Map<String, String> labels = new HashMap<>();
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// labels from request
if (resolver != null) {
try {
Map<String, String> customResolvedLabels = resolver.resolve(requestTemplate);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
}
// labels from rule expression
String peerServiceName = requestTemplate.feignTarget().name();
Map<String, String> ruleExpressionLabels = getRuleExpressionLabels(requestTemplate, peerServiceName);
labels.putAll(ruleExpressionLabels);
//local service labels
labels.putAll(metadataLocalProperties.getContent());
// Because when the label is placed in RequestTemplate.header,
// RequestTemplate will parse the header according to the regular, which conflicts with the expression.
// Avoid conflicts by escaping.
Map<String, String> escapeLabels = new HashMap<>(labels.size());
for (Map.Entry<String, String> entry : labels.entrySet()) {
String escapedKey = ExpressionLabelUtils.escape(entry.getKey());
String escapedValue = ExpressionLabelUtils.escape(entry.getValue());
escapeLabels.put(escapedKey, escapedValue);
}
// pass label by header
requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, JacksonUtils.serialize2Json(escapeLabels));
}
private Map<String, String> getRuleExpressionLabels(RequestTemplate requestTemplate, String peerService) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerService);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return FeignExpressionLabelUtils.resolve(requestTemplate, labelKeys);
}
}

@ -0,0 +1,62 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.resttemplate;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
/**
* Replace LoadBalancerInterceptor with PolarisLoadBalancerInterceptor.
* PolarisLoadBalancerInterceptor can pass routing context information.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory factory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.factory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LoadBalancerInterceptor) {
LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
RouterLabelResolver routerLabelResolver = this.factory.getBean(RouterLabelResolver.class);
MetadataLocalProperties metadataLocalProperties = this.factory.getBean(MetadataLocalProperties.class);
RouterRuleLabelResolver routerRuleLabelResolver = this.factory.getBean(RouterRuleLabelResolver.class);
return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory,
routerLabelResolver, metadataLocalProperties, routerRuleLabelResolver);
}
return bean;
}
}

@ -0,0 +1,143 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.resttemplate;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ExpressionLabelUtils;
import com.tencent.cloud.polaris.router.PolarisRouterContext;
import com.tencent.cloud.polaris.router.RouterRuleLabelResolver;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor capabilities.
* Parses the label from the request and puts it into the RouterContext for routing.
*
*@author lepdou 2022-05-18
*/
public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisLoadBalancerInterceptor.class);
private final LoadBalancerClient loadBalancer;
private final LoadBalancerRequestFactory requestFactory;
private final RouterLabelResolver resolver;
private final MetadataLocalProperties metadataLocalProperties;
private final RouterRuleLabelResolver routerRuleLabelResolver;
private final boolean isRibbonLoadBalanceClient;
public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory,
RouterLabelResolver resolver,
MetadataLocalProperties metadataLocalProperties,
RouterRuleLabelResolver routerRuleLabelResolver) {
super(loadBalancer, requestFactory);
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
this.resolver = resolver;
this.metadataLocalProperties = metadataLocalProperties;
this.routerRuleLabelResolver = routerRuleLabelResolver;
this.isRibbonLoadBalanceClient = loadBalancer instanceof RibbonLoadBalancerClient;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String peerServiceName = originalUri.getHost();
Assert.state(peerServiceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
if (isRibbonLoadBalanceClient) {
PolarisRouterContext routerContext = genRouterContext(request, body, peerServiceName);
return ((RibbonLoadBalancerClient) loadBalancer).execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution), routerContext);
}
return this.loadBalancer.execute(peerServiceName,
this.requestFactory.createRequest(request, body, execution));
}
private PolarisRouterContext genRouterContext(HttpRequest request, byte[] body, String peerServiceName) {
Map<String, String> labels = new HashMap<>();
// labels from downstream
Map<String, String> transitiveLabels = MetadataContextHolder.get()
.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
labels.putAll(transitiveLabels);
// labels from request
if (resolver != null) {
try {
Map<String, String> customResolvedLabels = resolver.resolve(request, body);
if (!CollectionUtils.isEmpty(customResolvedLabels)) {
labels.putAll(customResolvedLabels);
}
}
catch (Throwable t) {
LOGGER.error("[SCT][Router] revoke RouterLabelResolver occur some exception. ", t);
}
}
Map<String, String> ruleExpressionLabels = getExpressionLabels(request, peerServiceName);
if (!CollectionUtils.isEmpty(ruleExpressionLabels)) {
labels.putAll(ruleExpressionLabels);
}
//local service labels
labels.putAll(metadataLocalProperties.getContent());
PolarisRouterContext routerContext = new PolarisRouterContext();
routerContext.setLabels(labels);
return routerContext;
}
private Map<String, String> getExpressionLabels(HttpRequest request, String peerServiceName) {
Set<String> labelKeys = routerRuleLabelResolver.getExpressionLabelKeys(MetadataContext.LOCAL_NAMESPACE,
MetadataContext.LOCAL_SERVICE, peerServiceName);
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
return ExpressionLabelUtils.resolve(request, labelKeys);
}
}

@ -0,0 +1,48 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.spi;
import java.util.Map;
import feign.RequestTemplate;
import org.springframework.http.HttpRequest;
/**
* The spi for resolving labels from request.
*
* @author lepdou 2022-05-11
*/
public interface RouterLabelResolver {
/**
* resolve labels from feign request.
* @param requestTemplate the feign request.
* @return resolved labels
*/
Map<String, String> resolve(RequestTemplate requestTemplate);
/**
* resolve labels from rest template request.
* @param request the rest template request.
* @param body the rest template request body.
* @return resolved labels
*/
Map<String, String> resolve(HttpRequest request, byte[] body);
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.router.config.RouterAutoConfiguration

@ -83,6 +83,18 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

@ -26,28 +26,6 @@ import org.springframework.core.Ordered;
*/ */
public final class MetadataConstant { public final class MetadataConstant {
/**
* System metadata key.
*/
public static class SystemMetadataKey {
/**
* Peer namespace.
*/
public static String PEER_NAMESPACE = "PEER_NAMESPACE";
/**
* Peer service.
*/
public static String PEER_SERVICE = "PEER_SERVICE";
/**
* Peer path.
*/
public static String PEER_PATH = "PEER_PATH";
}
/** /**
* Order of filter, interceptor, ... * Order of filter, interceptor, ...
*/ */

@ -34,6 +34,10 @@ import org.springframework.util.StringUtils;
*/ */
public class MetadataContext { public class MetadataContext {
/**
* transitive context.
*/
public static final String FRAGMENT_TRANSITIVE = "transitive";
/** /**
* Namespace of local instance. * Namespace of local instance.
*/ */
@ -44,15 +48,8 @@ public class MetadataContext {
*/ */
public static String LOCAL_SERVICE; public static String LOCAL_SERVICE;
/**
* Transitive custom metadata content.
*/
private final Map<String, String> transitiveCustomMetadata;
/** private final Map<String, Map<String, String>> fragmentContexts;
* System metadata content.
*/
private final Map<String, String> systemMetadata;
static { static {
String namespace = ApplicationContextAwareUtils String namespace = ApplicationContextAwareUtils
@ -74,47 +71,43 @@ public class MetadataContext {
} }
public MetadataContext() { public MetadataContext() {
this.transitiveCustomMetadata = new ConcurrentHashMap<>(); this.fragmentContexts = new ConcurrentHashMap<>();
this.systemMetadata = new ConcurrentHashMap<>();
}
public Map<String, String> getAllTransitiveCustomMetadata() {
return Collections.unmodifiableMap(this.transitiveCustomMetadata);
} }
public String getTransitiveCustomMetadata(String key) {
return this.transitiveCustomMetadata.get(key);
}
public void putTransitiveCustomMetadata(String key, String value) { public Map<String, String> getFragmentContext(String fragment) {
this.transitiveCustomMetadata.put(key, value); Map<String, String> fragmentContext = fragmentContexts.get(fragment);
} if (fragmentContext == null) {
return Collections.emptyMap();
public void putAllTransitiveCustomMetadata(Map<String, String> customMetadata) { }
this.transitiveCustomMetadata.putAll(customMetadata); return Collections.unmodifiableMap(fragmentContext);
}
public Map<String, String> getAllSystemMetadata() {
return Collections.unmodifiableMap(this.systemMetadata);
} }
public String getSystemMetadata(String key) { public String getContext(String fragment, String key) {
return this.systemMetadata.get(key); Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
return null;
}
return fragmentContext.get(key);
} }
public void putSystemMetadata(String key, String value) { public void putContext(String fragment, String key, String value) {
this.systemMetadata.put(key, value); Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
fragmentContext = new ConcurrentHashMap<>();
fragmentContexts.put(fragment, fragmentContext);
}
fragmentContext.put(key, value);
} }
public void putAllSystemMetadata(Map<String, String> systemMetadata) { public void putFragmentContext(String fragment, Map<String, String> context) {
this.systemMetadata.putAll(systemMetadata); fragmentContexts.put(fragment, context);
} }
@Override @Override
public String toString() { public String toString() {
return "MetadataContext{" + "transitiveCustomMetadata=" return "MetadataContext{" +
+ JacksonUtils.serialize2Json(transitiveCustomMetadata) "fragmentContexts=" + JacksonUtils.serialize2Json(fragmentContexts) +
+ ", systemMetadata=" + JacksonUtils.serialize2Json(systemMetadata) + '}'; '}';
} }
} }

@ -13,6 +13,7 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.common.metadata; package com.tencent.cloud.common.metadata;
@ -57,7 +58,7 @@ public final class MetadataContextHolder {
Map<String, String> transitiveMetadataMap = getTransitiveMetadataMap( Map<String, String> transitiveMetadataMap = getTransitiveMetadataMap(
metadataLocalProperties.getContent(), metadataLocalProperties.getContent(),
metadataLocalProperties.getTransitive()); metadataLocalProperties.getTransitive());
metadataContext.putAllTransitiveCustomMetadata(transitiveMetadataMap); metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, transitiveMetadataMap);
METADATA_CONTEXT.set(metadataContext); METADATA_CONTEXT.set(metadataContext);
} }
@ -92,20 +93,15 @@ public final class MetadataContextHolder {
/** /**
* Save metadata map to thread local. * Save metadata map to thread local.
* @param customMetadataMap custom metadata collection * @param customMetadataMap custom metadata collection
* @param systemMetadataMap system metadata collection
*/ */
public static void init(Map<String, String> customMetadataMap, public static void init(Map<String, String> customMetadataMap) {
Map<String, String> systemMetadataMap) {
// Init ThreadLocal. // Init ThreadLocal.
MetadataContextHolder.remove(); MetadataContextHolder.remove();
MetadataContext metadataContext = MetadataContextHolder.get(); MetadataContext metadataContext = MetadataContextHolder.get();
// Save to ThreadLocal. // Save to ThreadLocal.
if (!CollectionUtils.isEmpty(customMetadataMap)) { if (!CollectionUtils.isEmpty(customMetadataMap)) {
metadataContext.putAllTransitiveCustomMetadata(customMetadataMap); metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, customMetadataMap);
}
if (!CollectionUtils.isEmpty(systemMetadataMap)) {
metadataContext.putAllSystemMetadata(systemMetadataMap);
} }
MetadataContextHolder.set(metadataContext); MetadataContextHolder.set(metadataContext);
} }

@ -18,10 +18,7 @@
package com.tencent.cloud.common.metadata.config; package com.tencent.cloud.common.metadata.config;
import com.netflix.zuul.ZuulFilter;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstZuulFilter;
import com.tencent.cloud.common.metadata.interceptor.feign.MetadataFirstFeignInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
@ -45,34 +42,6 @@ public class MetadataAutoConfiguration {
return new MetadataLocalProperties(); return new MetadataLocalProperties();
} }
/**
* Create when Feign exists.
*/
@Configuration
@ConditionalOnClass(name = "feign.Feign")
static class MetadataFeignInterceptorConfig {
@Bean
public MetadataFirstFeignInterceptor metadataFirstFeignInterceptor() {
return new MetadataFirstFeignInterceptor();
}
}
/**
* Create when gateway application is Zuul.
*/
@Configuration
@ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet")
static class MetadataZuulFilterConfig {
@Bean
public ZuulFilter metadataFirstZuulFilter() {
return new MetadataFirstZuulFilter();
}
}
/** /**
* Create when gateway application is SCG. * Create when gateway application is SCG.
*/ */

@ -25,12 +25,10 @@ import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/** /**
* Scg output first filter used for setting peer info in context. * Scg output first filter used for setting peer info in context.
@ -51,9 +49,6 @@ public class MetadataFirstScgFilter implements GlobalFilter, Ordered {
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// get request context
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
// get metadata of current thread // get metadata of current thread
MetadataContext metadataContext = exchange MetadataContext metadataContext = exchange
.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); .getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
@ -61,23 +56,6 @@ public class MetadataFirstScgFilter implements GlobalFilter, Ordered {
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
} }
// TODO The peer namespace is temporarily the same as the local namespace
metadataContext.putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_NAMESPACE,
MetadataContext.LOCAL_NAMESPACE);
if (route != null) {
metadataContext.putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_SERVICE,
route.getUri().getAuthority());
}
else {
metadataContext.putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_SERVICE,
exchange.getRequest().getURI().getAuthority());
}
metadataContext.putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_PATH,
exchange.getRequest().getURI().getPath());
exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT,
metadataContext); metadataContext);

@ -1,70 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.metadata.filter.gateway;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_URI_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
/**
* Zuul output first filter used for setting peer info in context.
*
* @author Haotian Zhang
*/
public class MetadataFirstZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER + 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
// get request context
RequestContext requestContext = RequestContext.getCurrentContext();
// TODO The peer namespace is temporarily the same as the local namespace
MetadataContextHolder.get().putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_NAMESPACE,
MetadataContext.LOCAL_NAMESPACE);
MetadataContextHolder.get().putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_SERVICE,
(String) requestContext.get(SERVICE_ID_KEY));
MetadataContextHolder.get().putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_PATH,
(String) requestContext.get(REQUEST_URI_KEY));
return null;
}
}

@ -1,55 +0,0 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.metadata.interceptor.feign;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.core.Ordered;
/**
* Interceptor used for setting peer info in context.
*
* @author Haotian Zhang
*/
public class MetadataFirstFeignInterceptor implements RequestInterceptor, Ordered {
@Override
public int getOrder() {
return MetadataConstant.OrderConstant.METADATA_FIRST_FEIGN_INTERCEPTOR_ORDER;
}
@Override
public void apply(RequestTemplate requestTemplate) {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
// TODO The peer namespace is temporarily the same as the local namespace
metadataContext.putSystemMetadata(
MetadataConstant.SystemMetadataKey.PEER_NAMESPACE,
MetadataContext.LOCAL_NAMESPACE);
metadataContext.putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_SERVICE,
requestTemplate.feignTarget().name());
metadataContext.putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_PATH,
requestTemplate.path());
}
}

@ -48,6 +48,7 @@ public class PolarisServer extends Server {
} }
this.serviceInstances = serviceInstances; this.serviceInstances = serviceInstances;
this.instance = instance; this.instance = instance;
this.setAlive(true);
this.metaInfo = new MetaInfo() { this.metaInfo = new MetaInfo() {
@Override @Override
public String getAppName() { public String getAppName() {

@ -0,0 +1,323 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
/**
* the utils for parse label expression.
*
*@author lepdou 2022-05-13
*/
public class ExpressionLabelUtils {
/**
* the expression prefix of header label.
*/
public static final String LABEL_HEADER_PREFIX = "${http.header.";
/**
* the length of expression header label prefix.
*/
public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length();
/**
* the expression prefix of query.
*/
public static final String LABEL_QUERY_PREFIX = "${http.query.";
/**
* the length of expression query label prefix.
*/
public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length();
/**
* the expression prefix of cookie.
*/
public static final String LABEL_COOKIE_PREFIX = "${http.cookie.";
/**
* the length of expression cookie label prefix.
*/
public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length();
/**
* the expression of method.
*/
public static final String LABEL_METHOD = "${http.method}";
/**
* the expression of uri.
*/
public static final String LABEL_URI = "${http.uri}";
/**
* the prefix of expression.
*/
public static final String LABEL_PREFIX = "${";
/**
* the suffix of expression.
*/
public static final String LABEL_SUFFIX = "}";
/**
* the escape prefix of label.
*/
public static final String LABEL_ESCAPE_PREFIX = "$$$$";
public static boolean isExpressionLabel(String labelKey) {
if (StringUtils.isEmpty(labelKey)) {
return false;
}
if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) ||
StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) {
return true;
}
return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) ||
StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX))
&& StringUtils.endsWith(labelKey, LABEL_SUFFIX);
}
public static String escape(String str) {
return StringUtils.replace(str, LABEL_PREFIX, LABEL_ESCAPE_PREFIX);
}
public static String unescape(String str) {
return StringUtils.replace(str, LABEL_ESCAPE_PREFIX, LABEL_PREFIX);
}
public static Map<String, String> resolve(HttpServletRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, request.getHeader(headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request.getQueryString(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) {
String cookieKey = parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethod());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, request.getRequestURI());
}
}
return labels;
}
public static Map<String, String> resolve(ServerWebExchange exchange, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) {
String cookieKey = parseCookieKey(labelKey);
if (StringUtils.isBlank(cookieKey)) {
continue;
}
labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, exchange.getRequest().getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, exchange.getRequest().getURI().getPath());
}
}
return labels;
}
public static Map<String, String> resolve(HttpRequest request, Set<String> labelKeys) {
if (CollectionUtils.isEmpty(labelKeys)) {
return Collections.emptyMap();
}
Map<String, String> labels = new HashMap<>();
for (String labelKey : labelKeys) {
if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) {
String headerKey = parseHeaderKey(labelKey);
if (StringUtils.isBlank(headerKey)) {
continue;
}
labels.put(labelKey, getHeaderValue(request, headerKey));
}
else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) {
String queryKey = parseQueryKey(labelKey);
if (StringUtils.isBlank(queryKey)) {
continue;
}
labels.put(labelKey, getQueryValue(request, queryKey));
}
else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) {
labels.put(labelKey, request.getMethodValue());
}
else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) {
labels.put(labelKey, request.getURI().getPath());
}
}
return labels;
}
public static String parseHeaderKey(String expression) {
return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1);
}
public static String parseQueryKey(String expression) {
return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1);
}
public static String parseCookieKey(String expression) {
return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1);
}
public static String getQueryValue(String queryString, String queryKey) {
if (StringUtils.isBlank(queryString)) {
return StringUtils.EMPTY;
}
String[] queries = StringUtils.split(queryString, "&");
if (queries == null || queries.length == 0) {
return StringUtils.EMPTY;
}
for (String query : queries) {
String[] queryKV = StringUtils.split(query, "=");
if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) {
return queryKV[1];
}
}
return StringUtils.EMPTY;
}
public static String getCookieValue(Cookie[] cookies, String key) {
if (cookies == null || cookies.length == 0) {
return StringUtils.EMPTY;
}
for (Cookie cookie : cookies) {
if (StringUtils.equals(cookie.getName(), key)) {
return cookie.getValue();
}
}
return StringUtils.EMPTY;
}
public static String getHeaderValue(ServerHttpRequest request, String key) {
String value = request.getHeaders().getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getQueryValue(ServerHttpRequest request, String key) {
MultiValueMap<String, String> queries = request.getQueryParams();
if (CollectionUtils.isEmpty(queries)) {
return StringUtils.EMPTY;
}
String value = queries.getFirst(key);
if (value == null) {
return StringUtils.EMPTY;
}
return value;
}
public static String getCookieValue(ServerHttpRequest request, String key) {
HttpCookie cookie = request.getCookies().getFirst(key);
if (cookie == null) {
return StringUtils.EMPTY;
}
return cookie.getValue();
}
public static String getHeaderValue(HttpRequest request, String key) {
HttpHeaders headers = request.getHeaders();
return headers.getFirst(key);
}
public static String getQueryValue(HttpRequest request, String key) {
String query = request.getURI().getQuery();
return getQueryValue(query, key);
}
public static String getFirstValue(Map<String, Collection<String>> valueMaps, String key) {
if (CollectionUtils.isEmpty(valueMaps)) {
return StringUtils.EMPTY;
}
Collection<String> values = valueMaps.get(key);
if (CollectionUtils.isEmpty(values)) {
return StringUtils.EMPTY;
}
for (String value : values) {
return value;
}
return StringUtils.EMPTY;
}
}

@ -13,6 +13,7 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.common.metadata; package com.tencent.cloud.common.metadata;
@ -29,7 +30,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
/** /**
* Test for {@link MetadataContextHolder} * Test for {@link MetadataContextHolder}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -45,10 +46,10 @@ public class MetadataContextHolderTest {
customMetadata.put("a", "1"); customMetadata.put("a", "1");
customMetadata.put("b", "2"); customMetadata.put("b", "2");
MetadataContext metadataContext = MetadataContextHolder.get(); MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.putAllTransitiveCustomMetadata(customMetadata); metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, customMetadata);
MetadataContextHolder.set(metadataContext); MetadataContextHolder.set(metadataContext);
customMetadata = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); customMetadata = MetadataContextHolder.get().getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1"); Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");
Assertions.assertThat(customMetadata.get("b")).isEqualTo("2"); Assertions.assertThat(customMetadata.get("b")).isEqualTo("2");
@ -58,10 +59,9 @@ public class MetadataContextHolderTest {
customMetadata.put("a", "1"); customMetadata.put("a", "1");
customMetadata.put("b", "22"); customMetadata.put("b", "22");
customMetadata.put("c", "3"); customMetadata.put("c", "3");
Map<String, String> systemMetadata = new HashMap<>(); MetadataContextHolder.init(customMetadata);
MetadataContextHolder.init(customMetadata, systemMetadata);
metadataContext = MetadataContextHolder.get(); metadataContext = MetadataContextHolder.get();
customMetadata = metadataContext.getAllTransitiveCustomMetadata(); customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1"); Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");
Assertions.assertThat(customMetadata.get("b")).isEqualTo("22"); Assertions.assertThat(customMetadata.get("b")).isEqualTo("22");
Assertions.assertThat(customMetadata.get("c")).isEqualTo("3"); Assertions.assertThat(customMetadata.get("c")).isEqualTo("3");

@ -19,8 +19,6 @@
package com.tencent.cloud.common.metadata.config; package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstZuulFilter;
import com.tencent.cloud.common.metadata.interceptor.feign.MetadataFirstFeignInterceptor;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
@ -30,7 +28,7 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
/** /**
* Test for {@link MetadataAutoConfiguration} * Test for {@link MetadataAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */
@ -50,20 +48,10 @@ public class MetadataAutoConfigurationTest {
this.applicationContextRunner this.applicationContextRunner
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
.hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataFeignInterceptorConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstFeignInterceptor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataZuulFilterConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstZuulFilter.class);
Assertions.assertThat(context).hasSingleBean( Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class); MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
.hasSingleBean(MetadataFirstScgFilter.class);
}); });
} }
@ -75,20 +63,10 @@ public class MetadataAutoConfigurationTest {
this.webApplicationContextRunner this.webApplicationContextRunner
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
.hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataFeignInterceptorConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstFeignInterceptor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataZuulFilterConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstZuulFilter.class);
Assertions.assertThat(context).hasSingleBean( Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class); MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
.hasSingleBean(MetadataFirstScgFilter.class);
}); });
} }
@ -100,20 +78,10 @@ public class MetadataAutoConfigurationTest {
this.reactiveWebApplicationContextRunner this.reactiveWebApplicationContextRunner
.withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class))
.run(context -> { .run(context -> {
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class);
.hasSingleBean(MetadataLocalProperties.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataFeignInterceptorConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstFeignInterceptor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataZuulFilterConfig.class);
Assertions.assertThat(context)
.hasSingleBean(MetadataFirstZuulFilter.class);
Assertions.assertThat(context).hasSingleBean( Assertions.assertThat(context).hasSingleBean(
MetadataAutoConfiguration.MetadataScgFilterConfig.class); MetadataAutoConfiguration.MetadataScgFilterConfig.class);
Assertions.assertThat(context) Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class);
.hasSingleBean(MetadataFirstScgFilter.class);
}); });
} }

@ -27,7 +27,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
/** /**
* Test for {@link MetadataLocalProperties} * Test for {@link MetadataLocalProperties}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -70,8 +70,8 @@
</developers> </developers>
<properties> <properties>
<revision>1.4.0-Hoxton.SR9-SNAPSHOT</revision> <revision>1.5.0-Hoxton.SR9-SNAPSHOT</revision>
<polaris.version>1.5.0-SNAPSHOT</polaris.version> <polaris.version>1.5.2</polaris.version>
<powermock.version>2.0.0</powermock.version> <powermock.version>2.0.0</powermock.version>
<!-- Maven Plugin Versions --> <!-- Maven Plugin Versions -->

@ -13,7 +13,7 @@ spring:
polaris: polaris:
namespace: dev namespace: dev
config: config:
address: grpc://9.134.122.18:8093 # the address of polaris config server address: grpc://127.0.0.1:8093 # the address of polaris config server
auto-refresh: true # auto refresh when config file changed auto-refresh: true # auto refresh when config file changed
groups: groups:
- name: ${spring.application.name} # group name - name: ${spring.application.name} # group name

@ -55,6 +55,10 @@ public class GatewayCalleeController {
/** /**
* Get metadata in HTTP header. * Get metadata in HTTP header.
*
* @param metadataStr metadata string
* @return metadata in HTTP header
* @throws UnsupportedEncodingException encoding exception
*/ */
@RequestMapping("/echo") @RequestMapping("/echo")
public String echoHeader( public String echoHeader(

@ -15,19 +15,52 @@ spring:
address: grpc://127.0.0.1:8091 address: grpc://127.0.0.1:8091
namespace: default namespace: default
enabled: true enabled: true
discovery:
service-list-refresh-interval: 1000
gateway: gateway:
discovery: discovery:
locator: locator:
enabled: true enabled: true
lowerCaseServiceId: false 'predicates[0]':
routes: name: Path
- id: GatewayCalleeService args:
uri: lb://GatewayCalleeService patterns: '''/'' + serviceId + ''/**'''
predicates: 'filters[0]':
- Path=/GatewayCalleeService/** name: RewritePath
filters: args:
- StripPrefix=1 regexp: '''/'' + serviceId + ''/(?<remaining>.*)'''
replacement: '''/$\{remaining}'''
'filters[1]':
name: Retry
args:
retries: 3
exceptions:
'[0]': '''java.net.ConnectException'''
'[1]': '''java.io.IOException'''
statuses:
'[0]': '''BAD_GATEWAY'''
'[1]': '''SERVICE_UNAVAILABLE'''
series:
'[0]': '''CLIENT_ERROR'''
methods:
'[0]': '''GET'''
'[1]': '''POST'''
'[2]': '''PUT'''
'[3]': '''DELETE'''
backoff:
firstBackoff: '''100ms'''
maxBackoff: '''500ms'''
factor: 2
basedOnPreviousValue: false
# routes:
# - id: GatewayCalleeService
# uri: lb://GatewayCalleeService
# predicates:
# - Path=/GatewayCalleeService/**
# filters:
# - StripPrefix=1
logging: logging:
level: level:
org.springframework.cloud.gateway: info org.springframework.cloud.gateway: info
com.tencent.cloud.polaris: debug

@ -22,7 +22,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -44,10 +45,10 @@ public class RouterCalleeController {
* Get information of callee. * Get information of callee.
* @return information of callee * @return information of callee
*/ */
@GetMapping("/info") @PostMapping("/info")
public String info() { public String info(String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port); LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called.", port); return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
} }
} }

@ -13,40 +13,41 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.loadbalancer.rule; package com.tencent.cloud.polaris.router.example;
import java.util.Arrays;
/** /**
* Load balance rule. * demo object.
* * @author lepdou 2022-05-12
* @author Haotian Zhang
*/ */
public enum PolarisLoadBalanceRule { public class User {
/** private String name;
* Weighted random load balance rule. private int age;
*/
WEIGHTED_RANDOM_RULE("weighted_random");
/** public String getName() {
* Load balance strategy. return name;
*/ }
final String policy;
PolarisLoadBalanceRule(String strategy) { public void setName(String name) {
this.policy = strategy; this.name = name;
} }
public static PolarisLoadBalanceRule fromStrategy(String strategy) { public int getAge() {
return Arrays.stream(values()).filter(t -> t.getPolicy().equals(strategy)) return age;
.findAny().orElse(WEIGHTED_RANDOM_RULE);
} }
public String getPolicy() { public void setAge(int age) {
return policy; this.age = age;
} }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
} }

@ -22,8 +22,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
@ -44,10 +46,10 @@ public class RouterCalleeController {
* Get information of callee. * Get information of callee.
* @return information of callee * @return information of callee
*/ */
@GetMapping("/info") @PostMapping("/info")
public String info() { public String info(@RequestParam("name") String name, @RequestBody User user) {
LOG.info("Discovery Service Callee [{}] is called.", port); LOG.info("Discovery Service Callee [{}] is called.", port);
return String.format("Discovery Service Callee [%s] is called.", port); return String.format("Discovery Service Callee [%s] is called. user = %s", port, user);
} }
} }

@ -0,0 +1,53 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.example;
/**
* demo object.
* @author lepdou 2022-05-12
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@ -22,6 +22,12 @@
<groupId>com.tencent.cloud</groupId> <groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId> <artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -0,0 +1,60 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.example;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.tencent.cloud.polaris.router.spi.RouterLabelResolver;
import feign.RequestTemplate;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Component;
/**
*
* Customize the business tag information obtained from the request
*
*@author lepdou 2022-05-12
*/
@Component
public class CustomRouterLabelResolver implements RouterLabelResolver {
private final Gson gson = new Gson();
@Override
public Map<String, String> resolve(RequestTemplate requestTemplate) {
Map<String, String> labels = new HashMap<>();
User user = gson.fromJson(new String(requestTemplate.body()), User.class);
labels.put("user", user.getName());
return labels;
}
@Override
public Map<String, String> resolve(HttpRequest request, byte[] body) {
Map<String, String> labels = new HashMap<>();
User user = gson.fromJson(new String(body), User.class);
labels.put("user", user.getName());
return labels;
}
}

@ -19,7 +19,9 @@
package com.tencent.cloud.polaris.router.example; package com.tencent.cloud.polaris.router.example;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/** /**
* Router callee feign client. * Router callee feign client.
@ -29,7 +31,7 @@ import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("RouterCalleeService") @FeignClient("RouterCalleeService")
public interface RouterCalleeService { public interface RouterCalleeService {
@GetMapping("/router/service/callee/info") @PostMapping("/router/service/callee/info")
String info(); String info(@RequestParam("name") String name, @RequestBody User user);
} }

@ -21,6 +21,7 @@ package com.tencent.cloud.polaris.router.example;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -44,8 +45,11 @@ public class RouterCallerController {
* @return info * @return info
*/ */
@GetMapping("/feign") @GetMapping("/feign")
public String feign() { public String feign(@RequestParam String name) {
return routerCalleeService.info(); User user = new User();
user.setName(name);
user.setAge(18);
return routerCalleeService.info(name, user);
} }
/** /**
@ -53,10 +57,12 @@ public class RouterCallerController {
* @return information of callee * @return information of callee
*/ */
@GetMapping("/rest") @GetMapping("/rest")
public String rest() { public String rest(@RequestParam String name) {
return restTemplate.getForObject( User user = new User();
"http://DiscoveryCalleeService/discovery/service/callee/info", user.setName(name);
String.class); user.setAge(18);
return restTemplate.postForObject(
"http://RouterCalleeService/router/service/callee/info?name={name}", user, String.class, name);
} }
/** /**

@ -0,0 +1,45 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.router.example;
/**
* demo object.
* @author lepdou 2022-05-12
*/
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

@ -4,6 +4,10 @@ spring:
application: application:
name: RouterCallerService name: RouterCallerService
cloud: cloud:
tencent:
metadata:
content:
k1: v1
polaris: polaris:
address: grpc://127.0.0.1:8091 address: grpc://127.0.0.1:8091
namespace: default namespace: default

@ -47,4 +47,8 @@ public class PolarisContextAutoConfiguration {
return new ModifyAddress(); return new ModifyAddress();
} }
@Bean
public ServiceRuleManager serviceRuleManager(SDKContext sdkContext) {
return new ServiceRuleManager(sdkContext);
}
} }

@ -0,0 +1,117 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.context;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.tencent.polaris.api.pojo.DefaultServiceEventKeysProvider;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.pojo.ServiceRule;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.client.flow.BaseFlow;
import com.tencent.polaris.client.flow.DefaultFlowControlParam;
import com.tencent.polaris.client.flow.FlowControlParam;
import com.tencent.polaris.client.flow.ResourcesResponse;
import com.tencent.polaris.client.pb.RateLimitProto;
import com.tencent.polaris.client.pb.RoutingProto;
/**
* the manager of service governance rules. for example: rate limit rule, router rules.
*
*@author lepdou 2022-05-13
*/
public class ServiceRuleManager {
private final SDKContext sdkContext;
private final FlowControlParam controlParam;
public ServiceRuleManager(SDKContext sdkContext) {
this.sdkContext = sdkContext;
controlParam = new DefaultFlowControlParam();
controlParam.setTimeoutMs(sdkContext.getConfig().getGlobal().getAPI().getTimeout());
controlParam.setMaxRetry(sdkContext.getConfig().getGlobal().getAPI().getMaxRetryTimes());
controlParam.setRetryIntervalMs(sdkContext.getConfig().getGlobal().getAPI().getRetryInterval());
}
public RateLimitProto.RateLimit getServiceRateLimitRule(String namespace, String service) {
ServiceEventKey serviceEventKey = new ServiceEventKey(new ServiceKey(namespace, service),
ServiceEventKey.EventType.RATE_LIMITING);
DefaultServiceEventKeysProvider svcKeysProvider = new DefaultServiceEventKeysProvider();
svcKeysProvider.setSvcEventKey(serviceEventKey);
ResourcesResponse resourcesResponse = BaseFlow
.syncGetResources(sdkContext.getExtensions(), true, svcKeysProvider, controlParam);
ServiceRule serviceRule = resourcesResponse.getServiceRule(serviceEventKey);
if (serviceRule != null) {
Object rule = serviceRule.getRule();
if (rule instanceof RateLimitProto.RateLimit) {
return (RateLimitProto.RateLimit) rule;
}
}
return null;
}
public List<RoutingProto.Route> getServiceRouterRule(String namespace, String sourceService, String dstService) {
Set<ServiceEventKey> routerKeys = new HashSet<>();
ServiceEventKey dstSvcEventKey = new ServiceEventKey(new ServiceKey(namespace, dstService),
ServiceEventKey.EventType.ROUTING);
routerKeys.add(dstSvcEventKey);
ServiceEventKey srcSvcEventKey = new ServiceEventKey(new ServiceKey(namespace, sourceService),
ServiceEventKey.EventType.ROUTING);
DefaultServiceEventKeysProvider svcKeysProvider = new DefaultServiceEventKeysProvider();
svcKeysProvider.setSvcEventKeys(routerKeys);
ResourcesResponse resourcesResponse = BaseFlow
.syncGetResources(sdkContext.getExtensions(), true, svcKeysProvider, controlParam);
List<RoutingProto.Route> rules = new ArrayList<>();
//get source service outbound rules.
ServiceRule sourceServiceRule = resourcesResponse.getServiceRule(srcSvcEventKey);
if (sourceServiceRule != null) {
Object rule = sourceServiceRule.getRule();
if (rule instanceof RoutingProto.Routing) {
rules.addAll(((RoutingProto.Routing) rule).getOutboundsList());
}
}
//get peer service inbound rules.
ServiceRule dstServiceRule = resourcesResponse.getServiceRule(dstSvcEventKey);
if (dstServiceRule != null) {
Object rule = dstServiceRule.getRule();
if (rule instanceof RoutingProto.Routing) {
rules.addAll(((RoutingProto.Routing) rule).getInboundsList());
}
}
return rules;
}
}

@ -0,0 +1,58 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.loadbalancer;
import java.util.ArrayList;
import java.util.List;
import com.netflix.loadbalancer.Server;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
/**
* load balancer utils.
*
*@author lepdou 2022-05-17
*/
public class LoadBalancerUtils {
public static ServiceInstances transferServersToServiceInstances(List<Server> servers) {
List<Instance> instances = new ArrayList<>(servers.size());
String serviceName = null;
for (Server server : servers) {
if (server instanceof PolarisServer) {
Instance instance = ((PolarisServer) server).getInstance();
instances.add(instance);
if (serviceName == null) {
serviceName = instance.getService();
}
}
}
ServiceKey serviceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, serviceName);
return new DefaultServiceInstances(serviceKey, instances);
}
}

@ -13,14 +13,15 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.loadbalancer; package com.tencent.cloud.polaris.loadbalancer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer; import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
@ -30,23 +31,17 @@ import com.netflix.loadbalancer.PollingServerListUpdater;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList; import com.netflix.loadbalancer.ServerList;
import com.tencent.cloud.common.constant.ContextConstant; import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.constant.MetadataConstant.SystemMetadataKey;
import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.pojo.PolarisServer; import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties;
import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceInstances; import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey; import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.rpc.GetAllInstancesRequest; import com.tencent.polaris.api.rpc.GetHealthyInstancesRequest;
import com.tencent.polaris.api.rpc.InstancesResponse; import com.tencent.polaris.api.rpc.InstancesResponse;
import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest;
import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -57,16 +52,13 @@ import org.apache.commons.lang.StringUtils;
*/ */
public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> { public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> {
private final RouterAPI routerAPI; private final ConsumerAPI consumerAPI;
private ConsumerAPI consumerAPI;
private PolarisLoadBalancerProperties polarisLoadBalancerProperties; private final PolarisLoadBalancerProperties polarisLoadBalancerProperties;
public PolarisLoadBalancer(IClientConfig config, IRule rule, IPing ping, ServerList<Server> serverList, public PolarisLoadBalancer(IClientConfig config, IRule rule, IPing ping, ServerList<Server> serverList,
RouterAPI routerAPI, ConsumerAPI consumerAPI, PolarisLoadBalancerProperties properties) { ConsumerAPI consumerAPI, PolarisLoadBalancerProperties properties) {
super(config, rule, ping, serverList, null, new PollingServerListUpdater()); super(config, rule, ping, serverList, null, new PollingServerListUpdater());
this.routerAPI = routerAPI;
this.consumerAPI = consumerAPI; this.consumerAPI = consumerAPI;
this.polarisLoadBalancerProperties = properties; this.polarisLoadBalancerProperties = properties;
} }
@ -80,46 +72,21 @@ public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> {
else { else {
serviceInstances = getExtendDiscoveryServiceInstances(); serviceInstances = getExtendDiscoveryServiceInstances();
} }
if (serviceInstances == null || CollectionUtils.isEmpty(serviceInstances.getInstances())) { if (serviceInstances == null || CollectionUtils.isEmpty(serviceInstances.getInstances())) {
return Collections.emptyList(); return Collections.emptyList();
} }
ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest();
processRoutersRequest.setDstInstances(serviceInstances); List<Server> servers = new LinkedList<>();
String srcNamespace = MetadataContext.LOCAL_NAMESPACE; for (Instance instance : serviceInstances.getInstances()) {
String srcService = MetadataContext.LOCAL_SERVICE; servers.add(new PolarisServer(serviceInstances, instance));
Map<String, String> transitiveCustomMetadata = MetadataContextHolder.get()
.getAllTransitiveCustomMetadata();
String method = MetadataContextHolder.get()
.getSystemMetadata(SystemMetadataKey.PEER_PATH);
processRoutersRequest.setMethod(method);
if (StringUtils.isNotBlank(srcNamespace) && StringUtils.isNotBlank(srcService)) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(srcNamespace);
serviceInfo.setService(srcService);
serviceInfo.setMetadata(transitiveCustomMetadata);
processRoutersRequest.setSourceService(serviceInfo);
}
ProcessRoutersResponse processRoutersResponse = routerAPI
.processRouters(processRoutersRequest);
ServiceInstances filteredServiceInstances = processRoutersResponse
.getServiceInstances();
List<Server> filteredInstances = new ArrayList<>();
for (Instance instance : filteredServiceInstances.getInstances()) {
filteredInstances.add(new PolarisServer(serviceInstances, instance));
} }
return filteredInstances;
return servers;
} }
private ServiceInstances getPolarisDiscoveryServiceInstances() { private ServiceInstances getPolarisDiscoveryServiceInstances() {
String serviceName = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.PEER_SERVICE); return getAllInstances(MetadataContext.LOCAL_NAMESPACE, name).toServiceInstances();
if (StringUtils.isBlank(serviceName)) {
List<Server> allServers = super.getAllServers();
if (CollectionUtils.isEmpty(allServers)) {
return null;
}
serviceName = ((PolarisServer) super.getAllServers().get(0)).getServiceInstances().getService();
}
return getAllInstances(MetadataContext.LOCAL_NAMESPACE, serviceName).toServiceInstances();
} }
private ServiceInstances getExtendDiscoveryServiceInstances() { private ServiceInstances getExtendDiscoveryServiceInstances() {
@ -128,26 +95,16 @@ public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> {
return null; return null;
} }
ServiceInstances serviceInstances; ServiceInstances serviceInstances;
String serviceName; if (StringUtils.isBlank(name)) {
// notice the difference between different service registries
if (StringUtils.isNotBlank(
allServers.get(0).getMetaInfo().getServiceIdForDiscovery())) {
serviceName = allServers.get(0).getMetaInfo().getServiceIdForDiscovery();
}
else {
serviceName = allServers.get(0).getMetaInfo().getAppName();
}
if (StringUtils.isBlank(serviceName)) {
throw new IllegalStateException( throw new IllegalStateException(
"PolarisLoadBalancer only Server with AppName or ServiceIdForDiscovery attribute"); "PolarisLoadBalancer only Server with AppName or ServiceIdForDiscovery attribute");
} }
ServiceKey serviceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, ServiceKey serviceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE, name);
serviceName);
List<Instance> instances = new ArrayList<>(8); List<Instance> instances = new ArrayList<>(8);
for (Server server : allServers) { for (Server server : allServers) {
DefaultInstance instance = new DefaultInstance(); DefaultInstance instance = new DefaultInstance();
instance.setNamespace(MetadataContext.LOCAL_NAMESPACE); instance.setNamespace(MetadataContext.LOCAL_NAMESPACE);
instance.setService(serviceName); instance.setService(name);
instance.setHealthy(server.isAlive()); instance.setHealthy(server.isAlive());
instance.setProtocol(server.getScheme()); instance.setProtocol(server.getScheme());
instance.setId(server.getId()); instance.setId(server.getId());
@ -173,10 +130,10 @@ public class PolarisLoadBalancer extends DynamicServerListLoadBalancer<Server> {
* @return list of instances * @return list of instances
*/ */
public InstancesResponse getAllInstances(String namespace, String serviceName) { public InstancesResponse getAllInstances(String namespace, String serviceName) {
GetAllInstancesRequest request = new GetAllInstancesRequest(); GetHealthyInstancesRequest request = new GetHealthyInstancesRequest();
request.setNamespace(namespace); request.setNamespace(namespace);
request.setService(serviceName); request.setService(serviceName);
return consumerAPI.getAllInstance(request); return consumerAPI.getHealthyInstancesInstance(request);
} }
} }

@ -16,86 +16,57 @@
* *
*/ */
package com.tencent.cloud.polaris.loadbalancer.rule; package com.tencent.cloud.polaris.loadbalancer;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.pojo.PolarisServer; import com.tencent.cloud.common.pojo.PolarisServer;
import com.tencent.polaris.api.config.consumer.LoadBalanceConfig; import com.tencent.polaris.api.config.consumer.LoadBalanceConfig;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances; import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.core.RouterAPI;
import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceRequest; import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceRequest;
import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse; import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
* Weighted random load balance strategy. * Polaris weighted load balancer.
* *
* @author Haotian Zhang *@author lepdou 2022-05-17
*/ */
public class PolarisWeightedRandomRule extends AbstractLoadBalancerRule { public class PolarisWeightedRule extends AbstractLoadBalancerRule {
private static final String POLICY = LoadBalanceConfig.LOAD_BALANCE_WEIGHTED_RANDOM; private final RouterAPI routerAPI;
@Autowired public PolarisWeightedRule(RouterAPI routerAPI) {
private RouterAPI polarisRouter; this.routerAPI = routerAPI;
}
@Override @Override
public void initWithNiwsConfig(IClientConfig clientConfig) { public void initWithNiwsConfig(IClientConfig clientConfig) {
} }
@Override @Override
public Server choose(Object key) { public Server choose(Object key) {
// 1. filter by router List<Server> servers = getLoadBalancer().getReachableServers();
List<Server> serversAfterRouter = getLoadBalancer().getReachableServers(); if (CollectionUtils.isEmpty(servers)) {
if (CollectionUtils.isEmpty(serversAfterRouter)) {
return null; return null;
} }
ServiceInstances serviceInstances = transferServersToServiceInstances( ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(servers);
serversAfterRouter);
// 2. filter by load balance
ProcessLoadBalanceRequest request = new ProcessLoadBalanceRequest(); ProcessLoadBalanceRequest request = new ProcessLoadBalanceRequest();
request.setDstInstances(serviceInstances); request.setDstInstances(serviceInstances);
request.setLbPolicy(POLICY); request.setLbPolicy(LoadBalanceConfig.LOAD_BALANCE_WEIGHTED_RANDOM);
ProcessLoadBalanceResponse processLoadBalanceResponse = polarisRouter
.processLoadBalance(request);
Instance targetInstance = processLoadBalanceResponse.getTargetInstance();
return new PolarisServer(serviceInstances, targetInstance);
}
ServiceInstances transferServersToServiceInstances(List<Server> servers) { ProcessLoadBalanceResponse processLoadBalanceResponse = routerAPI.processLoadBalance(request);
List<Instance> instances = new ArrayList<>(servers.size());
String serviceName = null;
for (Server server : servers) { Instance targetInstance = processLoadBalanceResponse.getTargetInstance();
if (server instanceof PolarisServer) {
Instance instance = ((PolarisServer) server).getInstance();
instances.add(instance);
if (serviceName == null) {
serviceName = instance.getService();
}
}
}
ServiceKey serviceKey = new ServiceKey(MetadataContext.LOCAL_NAMESPACE,
serviceName);
return new DefaultServiceInstances(serviceKey, instances); return new PolarisServer(serviceInstances, targetInstance);
} }
} }

@ -24,7 +24,6 @@ import com.tencent.polaris.factory.api.RouterAPIFactory;
import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
@ -46,15 +45,12 @@ import org.springframework.context.annotation.Configuration;
public class PolarisLoadBalancerAutoConfiguration { public class PolarisLoadBalancerAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean
public PolarisLoadBalancerProperties polarisLoadBalancerProperties() { public PolarisLoadBalancerProperties polarisLoadBalancerProperties() {
return new PolarisLoadBalancerProperties(); return new PolarisLoadBalancerProperties();
} }
@Bean(name = "polarisRoute") @Bean
@ConditionalOnMissingBean public RouterAPI routerAPI(SDKContext polarisContext) throws PolarisException {
public RouterAPI polarisRouter(SDKContext polarisContext) throws PolarisException {
return RouterAPIFactory.createRouterAPIByContext(polarisContext); return RouterAPIFactory.createRouterAPIByContext(polarisContext);
} }
} }

@ -37,7 +37,7 @@ public class PolarisLoadBalancerProperties {
/** /**
* Load balance strategy. * Load balance strategy.
*/ */
private String strategy = "weightedRandom"; private String strategy;
/** /**
* Type of discovery server. * Type of discovery server.

@ -13,6 +13,7 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * 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 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. * specific language governing permissions and limitations under the License.
*
*/ */
package com.tencent.cloud.polaris.loadbalancer.config; package com.tencent.cloud.polaris.loadbalancer.config;
@ -24,12 +25,8 @@ import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList; import com.netflix.loadbalancer.ServerList;
import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer; import com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancer;
import com.tencent.cloud.polaris.loadbalancer.rule.PolarisLoadBalanceRule;
import com.tencent.cloud.polaris.loadbalancer.rule.PolarisWeightedRandomRule;
import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.router.api.core.RouterAPI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -42,24 +39,11 @@ import org.springframework.context.annotation.Configuration;
public class PolarisRibbonClientConfiguration { public class PolarisRibbonClientConfiguration {
@Bean @Bean
@ConditionalOnMissingBean
public IRule polarisRibbonRule(
PolarisLoadBalancerProperties polarisLoadBalancerProperties) {
switch (PolarisLoadBalanceRule
.fromStrategy(polarisLoadBalancerProperties.getStrategy())) {
case WEIGHTED_RANDOM_RULE:
default:
return new PolarisWeightedRandomRule();
}
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer polarisLoadBalancer(IClientConfig iClientConfig, IRule iRule, public ILoadBalancer polarisLoadBalancer(IClientConfig iClientConfig, IRule iRule,
IPing iPing, ServerList<Server> serverList, RouterAPI polarisRouter, IPing iPing, ServerList<Server> serverList,
ConsumerAPI consumerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties) { ConsumerAPI consumerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties) {
return new PolarisLoadBalancer(iClientConfig, iRule, iPing, serverList, return new PolarisLoadBalancer(iClientConfig, iRule, iPing, serverList,
polarisRouter, consumerAPI, polarisLoadBalancerProperties); consumerAPI, polarisLoadBalancerProperties);
} }
} }

@ -31,7 +31,7 @@ import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Test for {@link PolarisLoadBalancerAutoConfiguration} * Test for {@link PolarisLoadBalancerAutoConfiguration}.
* *
* @author Haotian Zhang * @author Haotian Zhang
*/ */

@ -4,4 +4,6 @@
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> "https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions> <suppressions>
<suppress files=".*" checks="RegexpHeader"/> <suppress files=".*" checks="RegexpHeader"/>
<suppress files=".*" checks="HideUtilityClassConstructor"/>
<suppress files=".*" checks="IllegalImport"/>
</suppressions> </suppressions>
Loading…
Cancel
Save