feat:merge features from 1.5.2-Hoxton.SR9 except router. ()

pull/232/head
Haotian Zhang 3 years ago committed by GitHub
parent 422feff466
commit fc4bd10564
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,3 +2,4 @@
---
- [Add metadata transfer example.](https://github.com/Tencent/spring-cloud-tencent/pull/210)
- [feat:merge features from 1.5.2-Hoxton.SR9 except router.](https://github.com/Tencent/spring-cloud-tencent/pull/226)

@ -24,4 +24,4 @@ Please confirm before completing a PR:
4. Ensure a consistent code style.
5. Do adequate testing.
6. Add this pull request info to [CHANGELOG](./CHANGELOG.md).
7. Then, you can submit your code to the dev branch.
7. Then, you can submit your code to the dev branch.

@ -41,9 +41,6 @@ Copyright (c) guava authors and contributors.
6. reactor
Copyright (c) reactor authors and contributors.
7. powermock
Copyright 2007-2017 PowerMock Contributors
Terms of the Apache v2.0 License:
--------------------------------------------------------------------

@ -1,81 +1,109 @@
# Spring Cloud Tencent
[![Wiki](https://badgen.net/badge/icon/wiki?icon=wiki&label)](https://github.com/Tencent/spring-cloud-tencent/wiki)
[![Build Status](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml/badge.svg)](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.tencent.cloud/spring-cloud-tencent?label=Maven%20Central)](https://search.maven.org/search?q=g:com.tencent.cloud%20AND%20a:spring-cloud-tencent)
[![codecov.io](https://codecov.io/gh/Tencent/spring-cloud-tencent/branch/main/graph/badge.svg)](https://codecov.io/gh/Tencent/spring-cloud-tencent?branch=main)
[![Contributors](https://img.shields.io/github/contributors/Tencent/spring-cloud-tencent)](https://github.com/Tencent/spring-cloud-tencent/graphs/contributors)
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
[English](./README.md) | 简体中文
---
## 介绍
Spring Cloud Tencent包含了分布式应用微服务开发过程中所需的组件基于 Spring Cloud 框架的开发者可以使用这些组件快速进行分布式应用的开发。
## 主要功能
* **服务注册与发现**:基于 Spring Cloud Common的标准进行微服务的注册与发现。
* **服务路由与负载均衡**:基于 Ribbon 的接口标准,提供场景更丰富的动态路由以及负载均衡的能力。
* **故障节点熔断**:提供故障节点的熔断剔除以及主/被动探测恢复的能力,保证分布式服务的可靠性。
* **服务限流**:支持微服务被调接入层和网关主动调用的限流功能,保证后台微服务稳定性,可通过控制台动态配置规则,及查看流量监控数据。
* **元数据传递**: 支持网关及微服务应用之间的自定义元数据传递。
## 如何构建
* [2020.0.x](https://github.com/Tencent/spring-cloud-tencent/tree/2020.0.x)分支对应的是 Spring Cloud 2020.0版本编译环境最低支持JDK 1.8。
* [main](https://github.com/Tencent/spring-cloud-tencent/tree/main) 分支对应的是 Spring Cloud Hoxton版本编译环境最低支持JDK 1.8。
* [greenwich](https://github.com/Tencent/spring-cloud-tencent/tree/greenwich) 分支对应的是 Spring Cloud Greenwich版本编译环境最低支持JDK 1.8。
Spring Cloud Tencent 使用 Maven 来构建,最快的使用方式是将本项目 clone 到本地,然后执行以下命令:
```bash
./mvnw install
```
执行完毕后,项目将被安装到本地 Maven 仓库。
## 如何使用
### 如何引入依赖
在 dependencyManagement 中添加如下配置,然后在 dependencies 中添加自己所需使用的依赖即可使用。
Spring Cloud Tencent 是腾讯开源的一站式微服务解决方案。
Spring Cloud Tencent 实现了Spring Cloud 标准微服务 SPI开发者可以基于 Spring Cloud Tencent 快速开发 Spring Cloud 云原生分布式应用。
Spring Cloud Tencent 的核心依托腾讯开源的一站式服务发现与治理平台 [Polaris](https://github.com/polarismesh/polaris),实现各种分布式微服务场景。
- [Polaris Github home page](https://github.com/polarismesh/polaris)
- [Polaris official website](https://polarismesh.cn/)
Spring Cloud Tencent提供的能力包括但不限于
<img width="1029" alt="image" src="https://user-images.githubusercontent.com/4991116/170412323-ecaf544c-1d7b-45db-9cf0-591544e50c64.png">
- 服务注册和发现
- 动态配置管理
- 服务治理
- 服务限流
- 服务熔断
- 服务路由
- ...
- 标签透传
## 体验环境
- 管控台地址: http://14.116.241.63:8080/
- 账号polaris
- 密码polaris
- 控制面地址: `grpc://183.47.111.80:8091`
-
`spring-cloud-tencent-example` 下 example 地址都默认指向了体验服务地址(`grpc://183.47.111.80:8091`),如果您只是体验 Spring Cloud Tencent可直接一键运行任何 example。
## 管控台
<img width="1792" alt="image" src="https://user-images.githubusercontent.com/4991116/163402268-48493802-4555-4b93-8e31-011410f2166b.png">
## 使用指南
Spring Cloud Tencent 所有组件都已上传到 Maven 中央仓库,只需要引入依赖即可。
例如:
```` xml
<!-- add spring-cloud-tencent bom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- add spring-cloud-starter-tencent-polaris-discovery dependency -->
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
</dependencies>
````
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<version>1.1.4.Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
````
### 示例
Spring Cloud Tencent 项目包含了一个子模块spring-cloud-tencent-examples。此模块中提供了体验接入用的 example ,您可以阅读对应的 example 工程下的 readme 文档,根据里面的步骤来体验。
Example 列表:
- [PolarisMesh](https://github.com/polarismesh)接入相关的样例:
- ### 快速开始
- [Spring Cloud Tencent 版本管理](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent 服务注册与发现](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 配置中心](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 限流](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 熔断](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 服务路由](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent 标签传递](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- [服务发现](spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md)
- ### 开发文档
- [项目概览](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/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)
- [故障熔断](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md)
## 交流群
- [限流](spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md)
扫描下面的二维码加入 Spring Cloud Tencent 交流群。
- [网关](spring-cloud-tencent-examples/polaris-gateway-example/README-zh.md)
<img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
更多详细功能,请参考[polaris-java](https://github.com/polarismesh/polaris-java/blob/main/README-zh.md)。
## 版本号规范
## 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)
采取与Spring Cloud大版本号相关的版本策略。
## Stargazers over time
项目的版本号格式为 ```大版本号.小版本号.补丁版本号-对应Spring Cloud的大版本号.对应Spring Cloud的小版本号-发布类型``` 的形式。
大版本号、小版本号、补丁版本号的类型为数字,从 0 开始取值。
对应Spring Cloud的大版本号为Spring Cloud提供的英文版本号例如Hoxton、Greenwich等。对应Spring Cloud的小版本号为Spring Cloud给出的小版本号例如 RS9 等。
发布类型目前包括正式发布和发布候选版RC。在实际的版本号中正式发布版不额外添加发布类型发布候选版将添加后缀并从 RC0 开始。
如果您对 Spring Cloud Tencent 有兴趣,请关注我们的项目~
示例1.2.0-Hoxton.SR9-RC0
[![Stargazers over time](https://starchart.cc/Tencent/spring-cloud-tencent.svg)](https://starchart.cc/Tencent/spring-cloud-tencent)
## 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)

@ -1,89 +1,108 @@
# Spring Cloud Tencent
[![Wiki](https://badgen.net/badge/icon/wiki?icon=wiki&label)](https://github.com/Tencent/spring-cloud-tencent/wiki)
[![Build Status](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml/badge.svg)](https://github.com/Tencent/spring-cloud-tencent/actions/workflows/junit_test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.tencent.cloud/spring-cloud-tencent?label=Maven%20Central)](https://search.maven.org/search?q=g:com.tencent.cloud%20AND%20a:spring-cloud-tencent)
[![codecov.io](https://codecov.io/gh/Tencent/spring-cloud-tencent/branch/main/graph/badge.svg)](https://codecov.io/gh/Tencent/spring-cloud-tencent?branch=main)
[![Contributors](https://img.shields.io/github/contributors/Tencent/spring-cloud-tencent)](https://github.com/Tencent/spring-cloud-tencent/graphs/contributors)
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
English | [简体中文](./README-zh.md)
## Introduction
Spring Cloud Tencent contains components distributed micro-service applications need during developing phase, developers that built their key architectures based on Spring Cloud can use these components
Spring Cloud Tencent is a open source one-stop microservice solution from Tencent.
Based on Spring Cloud Tencent, you only need a small configuration to launch Spring Cloud and micro-service's joint solutions.
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.
## Key Features
The core of Spring Cloud Tencent relies on Tencent's open-source one-stop service discovery and governance platform [Polaris](https://github.com/polarismesh/polaris) to realize various distributed microservice scenarios.
* **Service Registration and Discovery**: Based on Spring Cloud's discovery and registration standard.
* **Service Routing and LoadBalancer**: Based on ribbon's API port, provide dynamic routing and load balancing use cases.
* **CircuitBreaker Node**: Support circuitbreaker auto-reset ability, ensure the reliability of distributed server
* **Rate Limiter**: Support rate limit of microservice and gateway, ensure the stability of backend, one can configure policies and traffic data from the control panel
* **Metadata Delivery**: Support metadata delivery between gateways and microservices.
- [Polaris Github home page](https://github.com/polarismesh/polaris)
- [Polaris official website](https://polarismesh.cn/)
## Components
The capabilities provided by Spring Cloud Tencent include but are not limited to:
**[Polaris](https://github.com/PolarisMesh/polaris)**Polaris Spring Cloud operation centre, provide solutions to registration, dynamic routing, load balancing and circuitbreaker.
<img width="1031" alt="image" src="https://user-images.githubusercontent.com/4991116/170412596-692f8dae-42f7-495f-a451-01396e381eb0.png">
## How to build
- Service registration and discovery
- Dynamic configuration management
- Service Governance
- Service rate limit
- Service circuit breaker
- Service routing
- ...
- Label transparent transmission
* master's branch matches Spring Cloud Hoxton, support lowest at JDK 1.8.
## Demo Environment
Spring Cloud Tencent uses Maven to construct, the fastest way is to clone project to local files, then execute the following orders:
- Console Address : http://14.116.241.63:8080/
- Username: polaris
- Password: polaris
- Server Address: `grpc://183.47.111.80:8091`
```bash
./mvnw install
```
The example addresses under `spring-cloud-tencent-example` all point to the experience service address (`grpc://183.47.111.80:8091`) by default.
If you only experience Spring Cloud Tencent, you can run any example directly with one click.
When all the steps are finished, the project will be installed in local Maven repository.
## Screenshots
## How to Use
<img width="1792" alt="image" src="https://user-images.githubusercontent.com/4991116/163402268-48493802-4555-4b93-8e31-011410f2166b.png">
### How to Introduce Dependency
## Use Guide
Add the following configurations in dependencyManagement, then add the dependencies you need.
At the same time, you need to pay attention to the Spring Cloud version corresponding to Spring Cloud Tencent, and then the corresponding Spring Boot version.
For example, Spring Cloud Tencent's 1.0.1.Hoxton.SR9 corresponds to the Spring Cloud Hoxton version and requires Spring Boot 2.3.x.
All the components of Spring Cloud Tencent have been uploaded to the Maven central repository, just need to introduce dependencies.
````
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
````
### Example
For example:
Spring Cloud Tencent project contains a sub-module spring-cloud-tencent-examples. This module provides examples for users to experience, you can read the README.md in each example, and follow the instructions there.
```` xml
<!-- add spring-cloud-tencent bom -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-dependencies</artifactId>
<!--version number-->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- add spring-cloud-starter-tencent-polaris-discovery dependency -->
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
</dependencies>
Example List:
- [Polaris Discovery Example](spring-cloud-tencent-examples/polaris-discovery-example/README.md)
````
- [Polaris CircuitBreaker Example](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md)
- ### Quick Start
- [Spring Cloud Tencent Version Management](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86)
- [Spring Cloud Tencent Discovery](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Discovery-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Config](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Config-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Rate Limit](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Rate-Limit-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent CircuitBreaker](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Circuitbreaker-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Router](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Router-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3)
- [Spring Cloud Tencent Metadata Transfer](https://github.com/Tencent/spring-cloud-tencent/wiki/Spring-Cloud-Tencent-Metadata-Transfer-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- [Polaris RateLimit Example](spring-cloud-tencent-examples/polaris-ratelimit-example/README.md)
- ### Development Documentation
- [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/%E5%8F%82%E4%B8%8E%E5%85%B1%E5%BB%BA)
## Chat Group
- [Polaris Gateway Example](spring-cloud-tencent-examples/polaris-gateway-example/README.md)
Please scan the QR code to join the chat group.
For more features, please refer to [polaris-java](https://github.com/polarismesh/polaris-java).
<img src="https://user-images.githubusercontent.com/24446200/169198148-d4cc3494-3485-4515-9897-c8cb5504f706.png" width="30%" height="30%" />
### Version Standard
## 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)
We use a version policy related to Spring Cloud's major version number.
Project version includes ```${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-${CORRESPONDING_MAJOR_VERSION_OF_SPRING_CLOUD}.${CORRESPONDING_MINOR_VERSION_OF_SPRING_CLOUD}-${RELEASE_TYPE}```.
```${MAJOR_VERSION}```, ```${MINOR_VERSION}```, ```${PATCH_VERSION}``` are in numbers starting from 0.
```${CORRESPONDING_MAJOR_VERSION_OF_SPRING_CLOUD}``` is the same as the major version number of Spring Cloud, like Hoxton, Greenwich. ```${CORRESPONDING_MINOR_VERSION_OF_SPRING_CLOUD}``` is the same as the major version number of Spring Cloud, like RS9.
```${RELEASE_TYPE}``` is like RELEASE or RC currently. Actually, the RELEASE version does not add a release type in the version, and the RS version will add a suffix and start from RC0.
## Stargazers over time
For example: 1.2.0-Hoxton.SR9-RC0
If you are interested in Spring Cloud Tencent, please follow our project, thank you very much.
## 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)
[![Stargazers over time](https://starchart.cc/Tencent/spring-cloud-tencent.svg)](https://starchart.cc/Tencent/spring-cloud-tencent)

@ -86,14 +86,11 @@
<properties>
<!-- Project revision -->
<revision>1.5.0-2020.0.5-SNAPSHOT</revision>
<revision>1.5.2-2020.0.5-SNAPSHOT</revision>
<!-- Spring Cloud -->
<spring.cloud.version>2020.0.5</spring.cloud.version>
<!-- Dependencies -->
<logback.version>1.2.7</logback.version>
<!-- Maven Plugin Versions -->
<jacoco.version>0.8.3</jacoco.version>
<maven-source-plugin.version>3.2.0</maven-source-plugin.version>
@ -109,15 +106,6 @@
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Tencent Dependencies -->
<dependency>
<groupId>com.tencent.cloud</groupId>
@ -127,10 +115,13 @@
<scope>import</scope>
</dependency>
<!-- Spring Cloud Dependencies -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

@ -38,7 +38,7 @@
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@ -50,20 +50,6 @@
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<scope>test</scope>
</dependency>
<!-- powermock-module-junit4 -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<!-- powermock-api-mockito -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,80 @@
/*
* 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.metadata.core;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* resolve custom transitive metadata from request.
*@author lepdou 2022-05-20
*/
public class CustomTransitiveMetadataResolver {
private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
public static Map<String, String> resolve(ServerWebExchange exchange) {
Map<String, String> result = new HashMap<>();
HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String key = entry.getKey();
if (StringUtils.isNotBlank(key) &&
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
}
return result;
}
public static Map<String, String> resolve(HttpServletRequest request) {
Map<String, String> result = new HashMap<>();
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String key = headers.nextElement();
if (StringUtils.isNotBlank(key) &&
StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
}
return result;
}
}

@ -20,6 +20,7 @@ package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
@ -56,8 +57,31 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
Map<String, String> internalTransitiveMetadata = getIntervalTransitiveMetadata(serverHttpRequest);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
MetadataContextHolder.init(mergedTransitiveMetadata);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
MetadataConstant.HeaderName.METADATA_CONTEXT,
MetadataContextHolder.get());
return webFilterChain.filter(serverWebExchange)
.doOnError(throwable -> LOG.error("handle metadata[{}] error.",
MetadataContextHolder.get(), throwable))
.doFinally((type) -> MetadataContextHolder.remove());
}
private Map<String, String> getIntervalTransitiveMetadata(ServerHttpRequest serverHttpRequest) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = httpHeaders.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
String customMetadataStr = httpHeaders
.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
try {
if (StringUtils.hasText(customMetadataStr)) {
customMetadataStr = URLDecoder.decode(customMetadataStr, "UTF-8");
@ -68,17 +92,7 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
}
LOG.debug("Get upstream metadata string: {}", customMetadataStr);
// create custom metadata.
Map<String, String> upstreamCustomMetadataMap = JacksonUtils.deserialize2Map(customMetadataStr);
MetadataContextHolder.init(upstreamCustomMetadataMap);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT,
MetadataContextHolder.get());
return webFilterChain.filter(serverWebExchange)
.doOnError(throwable -> LOG.error("handle metadata[{}] error.", MetadataContextHolder.get(), throwable))
.doFinally((type) -> MetadataContextHolder.remove());
return JacksonUtils.deserialize2Map(customMetadataStr);
}
}

@ -21,6 +21,7 @@ package com.tencent.cloud.metadata.core;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.FilterChain;
@ -50,8 +51,27 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
Map<String, String> internalTransitiveMetadata = getInternalTransitiveMetadata(httpServletRequest);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
try {
MetadataContextHolder.init(mergedTransitiveMetadata);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (IOException | ServletException | RuntimeException e) {
throw e;
}
}
private Map<String, String> getInternalTransitiveMetadata(HttpServletRequest httpServletRequest) {
// Get custom metadata string from http header.
String customMetadataStr = httpServletRequest.getHeader(MetadataConstant.HeaderName.CUSTOM_METADATA);
try {
@ -65,19 +85,7 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
LOG.debug("Get upstream metadata string: {}", customMetadataStr);
// create custom metadata.
Map<String, String> upstreamCustomMetadataMap = JacksonUtils.deserialize2Map(customMetadataStr);
try {
MetadataContextHolder.init(upstreamCustomMetadataMap);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
catch (IOException | ServletException | RuntimeException e) {
throw e;
}
finally {
MetadataContextHolder.remove();
}
return JacksonUtils.deserialize2Map(customMetadataStr);
}
}

@ -55,28 +55,17 @@ public class EncodeTransferMedataFeignInterceptor implements RequestInterceptor,
public void apply(RequestTemplate requestTemplate) {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
// add new metadata and cover old
if (!CollectionUtils.isEmpty(requestTemplate.headers())
&& !CollectionUtils.isEmpty(requestTemplate.headers().get(CUSTOM_METADATA))) {
for (String headerMetadataStr : requestTemplate.headers().get(CUSTOM_METADATA)) {
Map<String, String> headerMetadataMap = JacksonUtils.deserialize2Map(headerMetadataStr);
for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key));
}
}
}
Map<String, String> customMetadata = metadataContext.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata);
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
requestTemplate.removeHeader(CUSTOM_METADATA);
try {
requestTemplate.header(CUSTOM_METADATA, URLEncoder.encode(metadataStr, "UTF-8"));
requestTemplate.header(CUSTOM_METADATA, URLEncoder.encode(encodedTransitiveMetadata, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
LOG.error("Set header failed.", e);
requestTemplate.header(CUSTOM_METADATA, metadataStr);
requestTemplate.header(CUSTOM_METADATA, encodedTransitiveMetadata);
}
}
}

@ -27,7 +27,6 @@ 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.util.JacksonUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
@ -54,24 +53,16 @@ public class EncodeTransferMedataRestTemplateInterceptor implements ClientHttpRe
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
// get metadata of current thread
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
// add new metadata and cover old
String metadataStr = httpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA);
if (StringUtils.isNotBlank(metadataStr)) {
Map<String, String> headerMetadataMap = JacksonUtils.deserialize2Map(metadataStr);
for (String key : headerMetadataMap.keySet()) {
metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key));
}
}
Map<String, String> customMetadata = metadataContext.getAllTransitiveCustomMetadata();
if (!CollectionUtils.isEmpty(customMetadata)) {
metadataStr = JacksonUtils.serialize2Json(customMetadata);
String encodedTransitiveMetadata = JacksonUtils.serialize2Json(customMetadata);
try {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA,
URLEncoder.encode(metadataStr, "UTF-8"));
URLEncoder.encode(encodedTransitiveMetadata, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, metadataStr);
httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, encodedTransitiveMetadata);
}
}
return clientHttpRequestExecution.execute(httpRequest, bytes);

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

@ -41,7 +41,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = MOCK, classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
@SpringBootTest(webEnvironment = MOCK,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = { "spring.config.location = classpath:application-test.yml" })
public class DecodeTransferMetadataReactiveFilterTest {
@ -68,12 +69,15 @@ public class DecodeTransferMetadataReactiveFilterTest {
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("test")
.header(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}").build();
.header(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
metadataReactiveFilter.filter(exchange, webFilterChain);
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull();
}

@ -43,7 +43,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = DecodeTransferMetadataServletFilterTest.TestApplication.class,
properties = { "spring.config.location = classpath:application-test.yml" })
public class DecodeTransferMetadataServletFilterTest {
@ -65,8 +66,10 @@ public class DecodeTransferMetadataServletFilterTest {
request.addHeader(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}");
MockHttpServletResponse response = new MockHttpServletResponse();
metadataServletFilter.doFilter(request, response, filterChain);
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull();
}

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

@ -20,10 +20,8 @@ package com.tencent.cloud.metadata.core.intercepter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.stream.Collectors;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import feign.RequestInterceptor;
@ -32,17 +30,12 @@ import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@ -58,8 +51,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = DEFINED_PORT,
classes = EncodeTransferMedataFeignInterceptorTest.TestApplication.class,
properties = { "server.port=8081",
"spring.config.location = classpath:application-test.yml" })
properties = {"server.port=8081", "spring.config.location = classpath:application-test.yml"})
public class EncodeTransferMedataFeignInterceptorTest {
@Autowired
@ -71,21 +63,9 @@ public class EncodeTransferMedataFeignInterceptorTest {
@Test
public void test1() {
String metadata = testFeign.test();
Assertions.assertThat(metadata)
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a"))
.isEqualTo("11");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b"))
.isEqualTo("22");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c"))
.isEqualTo("33");
Assertions.assertThat(metadata).isEqualTo("{\"b\":\"2\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2");
}
@SpringBootApplication
@ -100,18 +80,13 @@ public class EncodeTransferMedataFeignInterceptorTest {
return URLDecoder.decode(customMetadataStr, "UTF-8");
}
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@FeignClient(name = "test-feign", url = "http://localhost:8081")
public interface TestFeign {
@RequestMapping(value = "/test",
headers = { MetadataConstant.HeaderName.CUSTOM_METADATA
+ "={\"a\":\"11" + "\",\"b\":\"22\",\"c\":\"33\"}" })
headers = {"X-SCT-Metadata-Transitive-a=11",
"X-SCT-Metadata-Transitive-b=22",
"X-SCT-Metadata-Transitive-c=33"})
String test();
}

@ -22,10 +22,8 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -34,9 +32,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@ -67,29 +62,29 @@ public class EncodeTransferMedataRestTemplateInterceptorTest {
@Test
public void test1() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA,
"{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
String metadata = restTemplate
.exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET,
httpEntity, String.class)
.getBody();
Assertions.assertThat(metadata)
.isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a"))
.isEqualTo("11");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b"))
.isEqualTo("22");
Assertions
.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c"))
.isEqualTo("33");
// HttpHeaders httpHeaders = new HttpHeaders();
// httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA,
// "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
// HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
// String metadata = restTemplate
// .exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET,
// httpEntity, String.class)
// .getBody();
// Assertions.assertThat(metadata)
// .isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}");
// Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
// .isEqualTo("1");
// Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
// .isEqualTo("2");
// Assertions
// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "a"))
// .isEqualTo("11");
// Assertions
// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"))
// .isEqualTo("22");
// Assertions
// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "c"))
// .isEqualTo("33");
}
@SpringBootApplication

@ -95,5 +95,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -13,7 +13,6 @@
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.circuitbreaker;

@ -30,6 +30,8 @@ import feign.Request;
import feign.Request.Options;
import feign.Response;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static feign.Util.checkNotNull;
@ -40,6 +42,9 @@ import static feign.Util.checkNotNull;
*/
public class PolarisFeignClient implements Client {
private static final Logger LOG = LoggerFactory.getLogger(PolarisFeignClient.class);
private final Client delegate;
private final ConsumerAPI consumerAPI;
@ -58,10 +63,13 @@ public class PolarisFeignClient implements Client {
if (response.status() >= 500) {
resultRequest.setRetStatus(RetStatus.RetFail);
}
LOG.debug("Will report result of {}. Request=[{}]. Response=[{}].",
resultRequest.getRetStatus().name(), request, response);
return response;
}
catch (IOException origin) {
resultRequest.setRetStatus(RetStatus.RetFail);
LOG.debug("Will report result of {}. Request=[{}].", resultRequest.getRetStatus().name(), request, origin);
throw origin;
}
finally {

@ -13,45 +13,32 @@
* 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;
package com.tencent.cloud.polaris.circuitbreaker;
import org.junit.Test;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
/**
* Test for {@link PolarisDiscoveryProperties}
* Test for {@link PolarisCircuitBreakerBootstrapConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisPropertiesTest {
public class PolarisCircuitBreakerBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PolarisCircuitBreakerBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testInitAndGetSet() {
PolarisDiscoveryProperties temp = new PolarisDiscoveryProperties();
try {
temp.setNamespace(NAMESPACE_TEST);
assertThat(temp.getNamespace()).isEqualTo(NAMESPACE_TEST);
temp.setService(SERVICE_PROVIDER);
assertThat(temp.getService()).isEqualTo(SERVICE_PROVIDER);
temp.setToken("xxxxxx");
assertThat(temp.getToken()).isEqualTo("xxxxxx");
temp.init();
assertThat(temp).isNotNull();
}
catch (Exception e) {
fail();
e.printStackTrace();
}
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisCircuitBreakerBootstrapConfiguration.CircuitBreakerConfigModifier.class);
});
}
}

@ -0,0 +1,52 @@
/*
* 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.circuitbreaker;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.polaris.api.core.ConsumerAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisFeignClientAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisFeignClientAutoConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisFeignClientAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisFeignBeanPostProcessor.class);
});
}
}

@ -0,0 +1,89 @@
/*
* 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.circuitbreaker.feign;
import com.tencent.polaris.api.core.ConsumerAPI;
import feign.Client;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test for {@link PolarisFeignBeanPostProcessor}.
*
* @author Haotian Zhang
*/
public class PolarisFeignBeanPostProcessorTest {
private PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor;
@Before
public void setUp() {
ConsumerAPI consumerAPI = mock(ConsumerAPI.class);
polarisFeignBeanPostProcessor = new PolarisFeignBeanPostProcessor(consumerAPI);
}
@Test
public void testPostProcessBeforeInitialization() {
BeanFactory beanFactory = mock(BeanFactory.class);
doAnswer(invocation -> {
Class<?> clazz = invocation.getArgument(0);
if (clazz.equals(BlockingLoadBalancerClient.class)) {
return mock(BlockingLoadBalancerClient.class);
}
if (clazz.equals(LoadBalancerProperties.class)) {
return mock(LoadBalancerProperties.class);
}
if (clazz.equals(LoadBalancerClientFactory.class)) {
return mock(LoadBalancerClientFactory.class);
}
return null;
}).when(beanFactory).getBean(any(Class.class));
polarisFeignBeanPostProcessor.setBeanFactory(beanFactory);
// isNeedWrap(bean) == false
Object bean1 = new Object();
Object bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean1, "bean1");
assertThat(bean).isNotInstanceOfAny(
PolarisFeignClient.class,
PolarisFeignBlockingLoadBalancerClient.class);
// bean instanceOf Client.class
Client bean2 = mock(Client.class);
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean2, "bean2");
assertThat(bean).isInstanceOf(PolarisFeignClient.class);
// bean instanceOf FeignBlockingLoadBalancerClient.class
FeignBlockingLoadBalancerClient bean3 = mock(FeignBlockingLoadBalancerClient.class);
doReturn(mock(Client.class)).when(bean3).getDelegate();
bean = polarisFeignBeanPostProcessor.postProcessBeforeInitialization(bean3, "bean3");
assertThat(bean).isInstanceOf(PolarisFeignBlockingLoadBalancerClient.class);
}
}

@ -0,0 +1,39 @@
/*
* 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.circuitbreaker.feign;
import org.assertj.core.api.Assertions;
import org.junit.Test;
/**
* Test for {@link PolarisFeignBlockingLoadBalancerClient}.
*
* @author Haotian Zhang
*/
public class PolarisFeignBlockingLoadBalancerClientTest {
@Test
public void testConstructor() {
try {
new PolarisFeignBlockingLoadBalancerClient(null, null, null, null);
}
catch (Exception e) {
Assertions.fail("Exception encountered.", e);
}
}
}

@ -17,48 +17,121 @@
package com.tencent.cloud.polaris.circuitbreaker.feign;
import com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import java.io.IOException;
import com.google.common.collect.Maps;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.rpc.ServiceCallResult;
import feign.Client;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.Target;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
/**
* Test for {@link PolarisFeignClient}.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestPolarisFeignApp.class)
@ContextConfiguration(classes = { PolarisFeignClientAutoConfiguration.class, PolarisContextAutoConfiguration.class })
@SpringBootTest(classes = PolarisFeignClientTest.TestApplication.class,
properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"})
public class PolarisFeignClientTest {
@Autowired
private ApplicationContext springCtx;
@Test
public void testPolarisFeignBeanPostProcessor() {
final PolarisFeignBeanPostProcessor postProcessor = springCtx.getBean(PolarisFeignBeanPostProcessor.class);
Assertions.assertNotNull(postProcessor, "PolarisFeignBeanPostProcessor");
public void testConstructor() {
try {
new PolarisFeignClient(null, null);
fail("NullPointerException should be thrown.");
}
catch (Throwable e) {
assertThat(e).isInstanceOf(NullPointerException.class);
assertThat(e.getMessage()).isEqualTo("target");
}
try {
new PolarisFeignClient(mock(Client.class), null);
fail("NullPointerException should be thrown.");
}
catch (Throwable e) {
assertThat(e).isInstanceOf(NullPointerException.class);
assertThat(e.getMessage()).isEqualTo("CircuitBreakAPI");
}
try {
assertThat(new PolarisFeignClient(mock(Client.class), mock(ConsumerAPI.class))).isInstanceOf(PolarisFeignClient.class);
}
catch (Throwable e) {
fail("Exception encountered.", e);
}
}
@Test
public void testFeignClient() {
final Client client = springCtx.getBean(Client.class);
if (client instanceof PolarisFeignClient) {
return;
public void testExecute() throws IOException {
// mock Client.class
Client delegate = mock(Client.class);
doAnswer(invocation -> {
Request request = invocation.getArgument(0);
if (request.httpMethod().equals(Request.HttpMethod.GET)) {
return Response.builder().request(request).status(200).build();
}
else if (request.httpMethod().equals(Request.HttpMethod.POST)) {
return Response.builder().request(request).status(500).build();
}
throw new IOException("Mock exception.");
}).when(delegate).execute(any(Request.class), nullable(Request.Options.class));
// mock ConsumerAPI.class
ConsumerAPI consumerAPI = mock(ConsumerAPI.class);
doNothing().when(consumerAPI).updateServiceCallResult(any(ServiceCallResult.class));
// mock target
Target<Object> target = Target.EmptyTarget.create(Object.class);
// mock RequestTemplate.class
RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.feignTarget(target);
PolarisFeignClient polarisFeignClient = new PolarisFeignClient(delegate, consumerAPI);
// 200
Response response = polarisFeignClient.execute(Request.create(Request.HttpMethod.GET, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
assertThat(response.status()).isEqualTo(200);
// 200
response = polarisFeignClient.execute(Request.create(Request.HttpMethod.POST, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
assertThat(response.status()).isEqualTo(500);
// Exception
try {
polarisFeignClient.execute(Request.create(Request.HttpMethod.DELETE, "http://localhost:8080/test",
Maps.newHashMap(), null, requestTemplate), null);
fail("IOException should be thrown.");
}
if (client instanceof PolarisFeignBlockingLoadBalancerClient) {
return;
catch (Throwable t) {
assertThat(t).isInstanceOf(IOException.class);
assertThat(t.getMessage()).isEqualTo("Mock exception.");
}
throw new IllegalStateException("Polaris burying failed");
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -1,61 +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.polaris.circuitbreaker.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Test application.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
@SpringBootApplication
@EnableFeignClients
public class TestPolarisFeignApp {
public static void main(String[] args) {
SpringApplication.run(TestPolarisFeignApp.class);
}
@FeignClient(name = "feign-service-polaris", fallback = TestPolarisServiceFallback.class)
public interface TestPolarisService {
/**
* Get info of service B.
*/
@GetMapping("/example/service/b/info")
String info();
}
@Component
public static class TestPolarisServiceFallback implements TestPolarisService {
@Override
public String info() {
return "trigger the refuse";
}
}
}

@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
/**
* polaris config module auto configuration at bootstrap phase.
@ -61,9 +62,9 @@ public class PolarisConfigBootstrapAutoConfiguration {
@Bean
public PolarisConfigFileLocator polarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager) {
PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
return new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService,
polarisPropertySourceManager);
polarisPropertySourceManager, environment);
}
@Bean

@ -1,23 +1,23 @@
/*
* 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.
* 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
* 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.
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.config.adapter;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -26,8 +26,10 @@ import com.tencent.cloud.polaris.config.config.ConfigFileGroup;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.ConfigFileFormat;
import com.tencent.cloud.polaris.context.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -61,13 +63,16 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final Environment environment;
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager) {
PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisContextProperties = polarisContextProperties;
this.configFileService = configFileService;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.environment = environment;
}
@Override
@ -80,19 +85,77 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
return compositePropertySource;
}
initPolarisConfigFiles(compositePropertySource, configFileGroups);
initInternalConfigFiles(compositePropertySource);
initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource;
}
private void initPolarisConfigFiles(CompositePropertySource compositePropertySource,
private void initInternalConfigFiles(CompositePropertySource compositePropertySource) {
List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles();
for (ConfigFileMetadata configFile : internalConfigFiles) {
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(
configFile.getNamespace(), configFile.getFileGroup(), configFile.getFileName());
compositePropertySource.addPropertySource(polarisPropertySource);
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file. file = {}", configFile);
}
}
private List<ConfigFileMetadata> getInternalConfigFiles() {
String namespace = polarisContextProperties.getNamespace();
String serviceName = polarisContextProperties.getService();
if (!StringUtils.hasText(serviceName)) {
serviceName = environment.getProperty("spring.application.name");
}
List<ConfigFileMetadata> internalConfigFiles = new LinkedList<>();
// priority: application-${profile} > application > boostrap-${profile} > boostrap
String[] activeProfiles = environment.getActiveProfiles();
for (String activeProfile : activeProfiles) {
if (!StringUtils.hasText(activeProfile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + activeProfile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + activeProfile + ".yml"));
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.yml"));
for (String activeProfile : activeProfiles) {
if (!StringUtils.hasText(activeProfile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + activeProfile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + activeProfile + ".yml"));
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yml"));
return internalConfigFiles;
}
private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource,
List<ConfigFileGroup> configFileGroups) {
String namespace = polarisContextProperties.getNamespace();
for (ConfigFileGroup configFileGroup : configFileGroups) {
String group = configFileGroup.getName();
if (StringUtils.isEmpty(group)) {
if (!StringUtils.hasText(group)) {
throw new IllegalArgumentException("polaris config group name cannot be empty.");
}

@ -99,17 +99,5 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -54,13 +54,13 @@ public class DiscoveryPropertiesAutoConfiguration {
private boolean discoveryEnabled = false;
@Bean(name = "polarisProvider")
@Bean
@ConditionalOnMissingBean
public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext);
}
@Bean(name = "polarisConsumer")
@Bean
@ConditionalOnMissingBean
public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException {
return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext);

@ -18,14 +18,11 @@
package com.tencent.cloud.polaris;
import javax.annotation.PostConstruct;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.factory.config.consumer.DiscoveryConfigImpl;
import com.tencent.polaris.factory.config.provider.RegisterConfigImpl;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -42,11 +39,6 @@ import org.springframework.core.env.Environment;
@ConfigurationProperties("spring.cloud.polaris.discovery")
public class PolarisDiscoveryProperties {
/**
* The polaris authentication token.
*/
private String token;
/**
* Namespace, separation registry of different environments.
*/
@ -59,6 +51,11 @@ public class PolarisDiscoveryProperties {
@Value("${spring.cloud.polaris.discovery.service:${spring.cloud.polaris.service:${spring.application.name:}}}")
private String service;
/**
* The polaris authentication token.
*/
private String token;
/**
* Load balance weight.
*/
@ -79,7 +76,7 @@ public class PolarisDiscoveryProperties {
/**
* Port of instance.
*/
@Value("${server.port:}")
@Value("${server.port:8080}")
private int port;
/**
@ -113,26 +110,7 @@ public class PolarisDiscoveryProperties {
@Autowired
private Environment environment;
/**
* Init properties.
*/
@PostConstruct
public void init() {
if (StringUtils.isEmpty(this.getNamespace())) {
this.setNamespace(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.namespace:}"));
}
if (StringUtils.isEmpty(this.getService())) {
this.setService(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.service:}"));
}
if (StringUtils.isEmpty(this.getToken())) {
this.setToken(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.token:}"));
}
}
public boolean isHeartbeatEnabled() {
if (null == heartbeatEnabled) {
return false;
}
return heartbeatEnabled;
}
@ -230,10 +208,20 @@ public class PolarisDiscoveryProperties {
@Override
public String toString() {
return "PolarisProperties{" + "token='" + token + '\'' + ", namespace='" + namespace + '\'' + ", service='"
+ service + '\'' + ", weight=" + weight + ", version='" + version + '\'' + ", protocol='" + protocol
+ '\'' + ", port=" + port + '\'' + ", registerEnabled=" + registerEnabled + ", heartbeatEnabled="
+ heartbeatEnabled + ", healthCheckUrl=" + healthCheckUrl + ", environment=" + environment + '}';
return "PolarisDiscoveryProperties{" +
"namespace='" + namespace + '\'' +
", service='" + service + '\'' +
", token='" + token + '\'' +
", weight=" + weight +
", version='" + version + '\'' +
", protocol='" + protocol + '\'' +
", port=" + port +
", enabled=" + enabled +
", registerEnabled=" + registerEnabled +
", heartbeatEnabled=" + heartbeatEnabled +
", healthCheckUrl='" + healthCheckUrl + '\'' +
", serviceListRefreshInterval=" + serviceListRefreshInterval +
'}';
}
@Bean

@ -32,7 +32,7 @@ public class PolarisDiscoveryClient implements DiscoveryClient {
/**
* Polaris Discovery Client Description.
*/
public final String description = "Spring Cloud Polaris Discovery Client";
public final String description = "Spring Cloud Tencent Polaris Discovery Client.";
private final PolarisServiceDiscovery polarisServiceDiscovery;

@ -18,22 +18,15 @@
package com.tencent.cloud.polaris.discovery;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.rpc.GetAllInstancesRequest;
import com.tencent.polaris.api.rpc.GetHealthyInstancesRequest;
import com.tencent.polaris.api.rpc.GetInstancesRequest;
import com.tencent.polaris.api.rpc.GetServicesRequest;
import com.tencent.polaris.api.rpc.InstancesResponse;
import com.tencent.polaris.api.rpc.ServicesResponse;
import com.tencent.polaris.client.api.SDKContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -58,32 +51,6 @@ public class PolarisDiscoveryHandler {
@Autowired
private ConsumerAPI polarisConsumer;
/**
* Get a list of instances after service routing.
* @param service service name
* @return list of instances
*/
@Deprecated
public InstancesResponse getFilteredInstances(String service) {
String namespace = polarisDiscoveryProperties.getNamespace();
GetInstancesRequest getInstancesRequest = new GetInstancesRequest();
getInstancesRequest.setNamespace(namespace);
getInstancesRequest.setService(service);
String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> allTransitiveCustomMetadata = MetadataContextHolder.get()
.getAllTransitiveCustomMetadata();
if (StringUtils.isNotBlank(localNamespace) || StringUtils.isNotBlank(localService)
|| null != allTransitiveCustomMetadata) {
ServiceInfo sourceService = new ServiceInfo();
sourceService.setNamespace(localNamespace);
sourceService.setService(localService);
sourceService.setMetadata(allTransitiveCustomMetadata);
getInstancesRequest.setServiceInfo(sourceService);
}
return polarisConsumer.getInstances(getInstancesRequest);
}
/**
* Get a list of healthy instances.
* @param service service name

@ -48,7 +48,7 @@ public class PolarisReactiveDiscoveryClient implements ReactiveDiscoveryClient {
@Override
public String description() {
return "Spring Cloud Polaris Reactive Discovery Client";
return "Spring Cloud Tencent Polaris Reactive Discovery Client.";
}
@Override

@ -1,3 +1,19 @@
/*
* 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;
@ -24,7 +40,7 @@ import static com.tencent.cloud.polaris.discovery.refresh.PolarisServiceStatusCh
*/
public class PolarisRefreshApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent>, ApplicationEventPublisherAware {
private static final Logger LOG = LoggerFactory.getLogger(PolarisRefreshConfiguration.class);
private static final Logger LOG = LoggerFactory.getLogger(PolarisRefreshApplicationReadyEventListener.class);
private static final int DELAY = 60;
private final PolarisDiscoveryHandler polarisDiscoveryHandler;
private final PolarisServiceStatusChangeListener polarisServiceStatusChangeListener;

@ -1,3 +1,19 @@
/*
* 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;

@ -1,3 +1,19 @@
/*
* 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;

@ -73,10 +73,18 @@ public class ConsulContextProperties {
@Value("${spring.cloud.consul.discovery.prefer-ip-address:#{'false'}}")
private boolean preferIpAddress;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}

@ -27,7 +27,7 @@ import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.context.annotation.Conditional;
/**
* @author Haotian Zhang
* @author Haotian Zhang, Andrew Shan, Jie Cheng
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })

@ -19,9 +19,10 @@
package com.tencent.cloud.polaris.registry;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.client.api.SDKContext;
@ -30,6 +31,7 @@ import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.util.CollectionUtils;
/**
* Registration object of Polaris.
@ -44,11 +46,16 @@ public class PolarisRegistration implements Registration, ServiceInstance {
private final SDKContext polarisContext;
private final StaticMetadataManager staticMetadataManager;
private Map<String, String> metadata;
public PolarisRegistration(DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration,
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context) {
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context, StaticMetadataManager staticMetadataManager) {
this.discoveryPropertiesAutoConfiguration = discoveryPropertiesAutoConfiguration;
this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.polarisContext = context;
this.staticMetadataManager = staticMetadataManager;
}
@Override
@ -82,7 +89,13 @@ public class PolarisRegistration implements Registration, ServiceInstance {
@Override
public Map<String, String> getMetadata() {
return Collections.emptyMap();
if (CollectionUtils.isEmpty(metadata)) {
metadata = new HashMap<>();
metadata.putAll(staticMetadataManager.getMergedStaticMetadata());
// location info will be putted both in metadata and instance's field
metadata.putAll(staticMetadataManager.getLocationMetadata());
}
return metadata;
}
public PolarisDiscoveryProperties getPolarisProperties() {
@ -95,8 +108,12 @@ public class PolarisRegistration implements Registration, ServiceInstance {
@Override
public String toString() {
return "PolarisRegistration{" + "polarisDiscoveryProperties=" + polarisDiscoveryProperties + ", polarisContext="
+ polarisContext + '}';
return "PolarisRegistration{" +
"discoveryPropertiesAutoConfiguration=" + discoveryPropertiesAutoConfiguration +
", polarisDiscoveryProperties=" + polarisDiscoveryProperties +
", polarisContext=" + polarisContext +
", staticMetadataManager=" + staticMetadataManager +
", metadata=" + metadata +
'}';
}
}

@ -22,7 +22,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.util.OkHttpUtil;
@ -59,16 +59,16 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
private final PolarisDiscoveryHandler polarisDiscoveryHandler;
private final MetadataLocalProperties metadataLocalProperties;
private final StaticMetadataManager staticMetadataManager;
private final ScheduledExecutorService heartbeatExecutor;
public PolarisServiceRegistry(PolarisDiscoveryProperties polarisDiscoveryProperties,
PolarisDiscoveryHandler polarisDiscoveryHandler,
MetadataLocalProperties metadataLocalProperties) {
StaticMetadataManager staticMetadataManager) {
this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.polarisDiscoveryHandler = polarisDiscoveryHandler;
this.metadataLocalProperties = metadataLocalProperties;
this.staticMetadataManager = staticMetadataManager;
if (polarisDiscoveryProperties.isHeartbeatEnabled()) {
this.heartbeatExecutor = Executors
@ -94,10 +94,13 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
instanceRegisterRequest.setPort(registration.getPort());
instanceRegisterRequest.setWeight(polarisDiscoveryProperties.getWeight());
instanceRegisterRequest.setToken(polarisDiscoveryProperties.getToken());
instanceRegisterRequest.setRegion(staticMetadataManager.getRegion());
instanceRegisterRequest.setZone(staticMetadataManager.getZone());
instanceRegisterRequest.setCampus(staticMetadataManager.getCampus());
if (null != heartbeatExecutor) {
instanceRegisterRequest.setTtl(ttl);
}
instanceRegisterRequest.setMetadata(metadataLocalProperties.getContent());
instanceRegisterRequest.setMetadata(registration.getMetadata());
instanceRegisterRequest.setProtocol(polarisDiscoveryProperties.getProtocol());
instanceRegisterRequest.setVersion(polarisDiscoveryProperties.getVersion());
try {
@ -105,7 +108,7 @@ public class PolarisServiceRegistry implements ServiceRegistry<Registration> {
providerClient.register(instanceRegisterRequest);
log.info("polaris registry, {} {} {}:{} {} register finished", polarisDiscoveryProperties.getNamespace(),
registration.getServiceId(), registration.getHost(), registration.getPort(),
metadataLocalProperties.getContent());
staticMetadataManager.getMergedStaticMetadata());
if (null != heartbeatExecutor) {
InstanceHeartbeatRequest heartbeatRequest = new InstanceHeartbeatRequest();

@ -18,7 +18,7 @@
package com.tencent.cloud.polaris.registry;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration;
@ -44,23 +44,25 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties
@ConditionalOnPolarisRegisterEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class,
PolarisDiscoveryAutoConfiguration.class })
@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class,
PolarisDiscoveryAutoConfiguration.class})
public class PolarisServiceRegistryAutoConfiguration {
@Bean
public PolarisServiceRegistry polarisServiceRegistry(
PolarisDiscoveryProperties polarisDiscoveryProperties, PolarisDiscoveryHandler polarisDiscoveryHandler,
MetadataLocalProperties metadataLocalProperties) {
return new PolarisServiceRegistry(polarisDiscoveryProperties, polarisDiscoveryHandler, metadataLocalProperties);
StaticMetadataManager staticMetadataManager) {
return new PolarisServiceRegistry(polarisDiscoveryProperties, polarisDiscoveryHandler, staticMetadataManager);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public PolarisRegistration polarisRegistration(
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration,
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context) {
return new PolarisRegistration(discoveryPropertiesAutoConfiguration, polarisDiscoveryProperties, context);
PolarisDiscoveryProperties polarisDiscoveryProperties, SDKContext context,
StaticMetadataManager staticMetadataManager) {
return new PolarisRegistration(discoveryPropertiesAutoConfiguration,
polarisDiscoveryProperties, context, staticMetadataManager);
}
@Bean

@ -0,0 +1,90 @@
/*
* 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;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler;
import com.tencent.cloud.polaris.extend.consul.ConsulContextProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.core.ProviderAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link DiscoveryPropertiesAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class DiscoveryPropertiesAutoConfigurationTest {
@Test
public void testDefaultInitialization() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
DiscoveryPropertiesAutoConfiguration.class));
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
assertThat(context).hasSingleBean(PolarisDiscoveryProperties.class);
assertThat(context).hasSingleBean(ConsulContextProperties.class);
assertThat(context).hasSingleBean(ProviderAPI.class);
assertThat(context).hasSingleBean(ConsumerAPI.class);
assertThat(context).hasSingleBean(PolarisDiscoveryHandler.class);
assertThat(context).hasSingleBean(DiscoveryConfigModifier.class);
});
}
@Test
public void testInit() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
TestConfiguration.class,
DiscoveryPropertiesAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.discovery.register=false")
.withPropertyValues("spring.cloud.consul.discovery.register=false")
.withPropertyValues("spring.cloud.consul.discovery.enabled=false");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration = context.getBean(DiscoveryPropertiesAutoConfiguration.class);
assertThat(discoveryPropertiesAutoConfiguration.isRegisterEnabled()).isFalse();
assertThat(discoveryPropertiesAutoConfiguration.isDiscoveryEnabled()).isFalse();
});
}
@Configuration
static class TestConfiguration {
@Bean
public PolarisDiscoveryProperties polarisDiscoveryProperties() {
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
polarisDiscoveryProperties.setEnabled(false);
return polarisDiscoveryProperties;
}
@Bean
public ConsulContextProperties consulContextProperties() {
ConsulContextProperties consulContextProperties = new ConsulContextProperties();
consulContextProperties.setEnabled(true);
return consulContextProperties;
}
}
}

@ -0,0 +1,46 @@
/*
* 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;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link DiscoveryPropertiesBootstrapAutoConfiguration}.
*
* @author Haotian Zhang
*/
public class DiscoveryPropertiesBootstrapAutoConfigurationTest {
@Test
public void testDefaultInitialization() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withConfiguration(
AutoConfigurations.of(PolarisContextAutoConfiguration.class,
DiscoveryPropertiesBootstrapAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.enabled=true");
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DiscoveryPropertiesBootstrapAutoConfiguration.class);
assertThat(context).hasSingleBean(DiscoveryPropertiesAutoConfiguration.class);
});
}
}

@ -0,0 +1,102 @@
/*
* 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;
import org.junit.Test;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.PROVIDER_TOKEN;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisDiscoveryProperties}.
*
* @author Haotian Zhang
*/
public class PolarisDiscoveryPropertiesTest {
@Test
public void testGetAndSet() {
PolarisDiscoveryProperties polarisDiscoveryProperties = new PolarisDiscoveryProperties();
// HeartbeatEnabled
polarisDiscoveryProperties.setHeartbeatEnabled(true);
assertThat(polarisDiscoveryProperties.isHeartbeatEnabled()).isTrue();
// Namespace
polarisDiscoveryProperties.setNamespace(NAMESPACE_TEST);
assertThat(polarisDiscoveryProperties.getNamespace()).isEqualTo(NAMESPACE_TEST);
// Weight
polarisDiscoveryProperties.setWeight(10);
assertThat(polarisDiscoveryProperties.getWeight()).isEqualTo(10);
// Service
polarisDiscoveryProperties.setService(SERVICE_PROVIDER);
assertThat(polarisDiscoveryProperties.getService()).isEqualTo(SERVICE_PROVIDER);
// Enabled
polarisDiscoveryProperties.setEnabled(true);
assertThat(polarisDiscoveryProperties.isEnabled()).isTrue();
// RegisterEnabled
polarisDiscoveryProperties.setRegisterEnabled(true);
assertThat(polarisDiscoveryProperties.isRegisterEnabled()).isTrue();
// Token
polarisDiscoveryProperties.setToken(PROVIDER_TOKEN);
assertThat(polarisDiscoveryProperties.getToken()).isEqualTo(PROVIDER_TOKEN);
// Version
polarisDiscoveryProperties.setVersion("1.0.0");
assertThat(polarisDiscoveryProperties.getVersion()).isEqualTo("1.0.0");
// HTTP
polarisDiscoveryProperties.setProtocol("HTTP");
assertThat(polarisDiscoveryProperties.getProtocol()).isEqualTo("HTTP");
// Port
polarisDiscoveryProperties.setPort(PORT);
assertThat(polarisDiscoveryProperties.getPort()).isEqualTo(PORT);
// HealthCheckUrl
polarisDiscoveryProperties.setHealthCheckUrl("/health");
assertThat(polarisDiscoveryProperties.getHealthCheckUrl()).isEqualTo("/health");
// ServiceListRefreshInterval
polarisDiscoveryProperties.setServiceListRefreshInterval(1000L);
assertThat(polarisDiscoveryProperties.getServiceListRefreshInterval()).isEqualTo(1000L);
assertThat(polarisDiscoveryProperties.toString())
.isEqualTo("PolarisDiscoveryProperties{"
+ "namespace='Test'"
+ ", service='java_provider_test'"
+ ", token='19485a7674294e3c88dba293373c1534'"
+ ", weight=10, version='1.0.0'"
+ ", protocol='HTTP'"
+ ", port=9091"
+ ", enabled=true"
+ ", registerEnabled=true"
+ ", heartbeatEnabled=true"
+ ", healthCheckUrl='/health'"
+ ", serviceListRefreshInterval=1000}");
}
}

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

@ -24,8 +24,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.client.ServiceInstance;
@ -37,12 +36,11 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisDiscoveryClient}
* Test for {@link PolarisDiscoveryClient}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@RunWith(MockitoJUnitRunner.class)
public class PolarisDiscoveryClientTest {
@Mock
@ -73,4 +71,8 @@ public class PolarisDiscoveryClientTest {
}
@Test
public void testDescription() {
assertThat(client.description()).isEqualTo("Spring Cloud Tencent Polaris Discovery Client.");
}
}

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

@ -20,13 +20,13 @@ package com.tencent.cloud.polaris.discovery.reactive;
import java.util.Arrays;
import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
@ -34,44 +34,73 @@ import org.springframework.cloud.client.ServiceInstance;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisReactiveDiscoveryClient}
* Test for {@link PolarisReactiveDiscoveryClient}.
*
* @author Haotian Zhang
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@RunWith(MockitoJUnitRunner.class)
public class PolarisReactiveDiscoveryClientTest {
@Mock
private PolarisServiceDiscovery serviceDiscovery;
@Mock
private ServiceInstance serviceInstance;
@InjectMocks
private PolarisReactiveDiscoveryClient client;
private int count = 0;
@Test
public void testGetInstances() throws PolarisException {
when(serviceDiscovery.getInstances(SERVICE_PROVIDER)).thenReturn(singletonList(serviceInstance));
when(serviceDiscovery.getInstances(anyString())).thenAnswer(invocation -> {
String serviceName = invocation.getArgument(0);
if (SERVICE_PROVIDER.equalsIgnoreCase(serviceName)) {
return singletonList(mock(ServiceInstance.class));
}
else {
throw new PolarisException(ErrorCode.UNKNOWN_SERVER_ERROR);
}
});
// Normal
Flux<ServiceInstance> instances = this.client.getInstances(SERVICE_PROVIDER);
StepVerifier.create(instances).expectNextCount(1).expectComplete().verify();
// PolarisException
instances = this.client.getInstances(SERVICE_PROVIDER + 1);
StepVerifier.create(instances).expectNextCount(0).expectComplete().verify();
}
@Test
public void testGetServices() throws PolarisException {
when(serviceDiscovery.getServices()).thenReturn(Arrays.asList(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2));
when(serviceDiscovery.getServices()).thenAnswer(invocation -> {
if (count == 0) {
count++;
return Arrays.asList(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2);
}
else {
throw new PolarisException(ErrorCode.UNKNOWN_SERVER_ERROR);
}
});
// Normal
Flux<String> services = this.client.getServices();
StepVerifier.create(services).expectNext(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2).expectComplete().verify();
// PolarisException
services = this.client.getServices();
StepVerifier.create(services).expectNextCount(0).expectComplete().verify();
}
@Test
public void testDescription() {
assertThat(client.description()).isEqualTo("Spring Cloud Tencent Polaris Reactive Discovery Client.");
}
}

@ -0,0 +1,114 @@
/*
* 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.lang.reflect.Field;
import java.util.Collections;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.pojo.ServiceInfo;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.pojo.ServiceInstancesByProto;
import com.tencent.polaris.client.pojo.ServicesByProto;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Test for {@link PolarisServiceStatusChangeListener}.
*
* @author Haotian Zhang
*/
public class PolarisServiceStatusChangeListenerTest {
private ApplicationEventPublisher publisher;
@Before
public void setUp() {
publisher = mock(ApplicationEventPublisher.class);
doNothing().when(publisher).publishEvent(any(ApplicationEvent.class));
}
@Test
public void testOnResourceUpdated() {
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener = new PolarisServiceStatusChangeListener();
polarisServiceStatusChangeListener.setApplicationEventPublisher(publisher);
// Service update event
ServiceEventKey serviceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.SERVICE);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setNamespace(NAMESPACE_TEST);
serviceInfo.setService(SERVICE_PROVIDER);
// Need update
ServicesByProto oldServices = new ServicesByProto(Collections.emptyList());
ServicesByProto newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
// No need update
oldServices = new ServicesByProto(Collections.singletonList(serviceInfo));
newServices = new ServicesByProto(Collections.singletonList(serviceInfo));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldServices, newServices);
verify(publisher, times(1)).publishEvent(any(ApplicationEvent.class));
// Instance update event
ServiceEventKey instanceUpdateEventKey = new ServiceEventKey(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER), ServiceEventKey.EventType.INSTANCE);
DefaultInstance instance = new DefaultInstance();
instance.setNamespace(NAMESPACE_TEST);
instance.setService(SERVICE_PROVIDER);
instance.setHost(HOST);
instance.setPort(PORT);
try {
Field instances = ServiceInstancesByProto.class.getDeclaredField("instances");
instances.setAccessible(true);
// Need update
ServiceInstancesByProto oldInstances = new ServiceInstancesByProto();
instances.set(oldInstances, Collections.emptyList());
ServiceInstancesByProto newInstances = new ServiceInstancesByProto();
instances.set(newInstances, Collections.singletonList(instance));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
// No need update
oldInstances = new ServiceInstancesByProto();
instances.set(oldInstances, Collections.singletonList(instance));
newInstances = new ServiceInstancesByProto();
instances.set(newInstances, Collections.singletonList(instance));
polarisServiceStatusChangeListener.onResourceUpdated(serviceUpdateEventKey, oldInstances, newInstances);
verify(publisher, times(2)).publishEvent(any(ApplicationEvent.class));
}
catch (NoSuchFieldException | IllegalAccessException e) {
Assertions.fail("Exception encountered.", e);
}
}
}

@ -0,0 +1,90 @@
/*
* 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.extend.consul;
import java.util.List;
import java.util.Map;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.factory.config.global.ServerConnectorConfigImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.INSTANCE_ID_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.IP_ADDRESS_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.PREFER_IP_ADDRESS_KEY;
import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.SERVICE_NAME_KEY;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ConsulContextProperties}.
*
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsulContextPropertiesTest.TestApplication.class)
@ActiveProfiles("test")
public class ConsulContextPropertiesTest {
@Autowired
private ConsulContextProperties consulContextProperties;
@Autowired
private SDKContext sdkContext;
@Test
public void testDefaultInitialization() {
assertThat(consulContextProperties).isNotNull();
assertThat(consulContextProperties.isEnabled()).isTrue();
assertThat(consulContextProperties.getHost()).isEqualTo("127.0.0.1");
assertThat(consulContextProperties.getPort()).isEqualTo(8500);
assertThat(consulContextProperties.isRegister()).isTrue();
assertThat(consulContextProperties.isDiscoveryEnabled()).isTrue();
}
@Test
public void testModify() {
assertThat(sdkContext).isNotNull();
com.tencent.polaris.api.config.Configuration configuration = sdkContext.getConfig();
List<ServerConnectorConfigImpl> serverConnectorConfigs = configuration.getGlobal().getServerConnectors();
Map<String, String> metadata = null;
for (ServerConnectorConfigImpl serverConnectorConfig : serverConnectorConfigs) {
if (serverConnectorConfig.getId().equals("consul")) {
metadata = serverConnectorConfig.getMetadata();
}
}
assertThat(metadata).isNotNull();
assertThat(metadata.get(SERVICE_NAME_KEY)).isEqualTo(SERVICE_PROVIDER);
assertThat(metadata.get(INSTANCE_ID_KEY)).isEqualTo("ins-test");
assertThat(metadata.get(PREFER_IP_ADDRESS_KEY)).isEqualTo("true");
assertThat(metadata.get(IP_ADDRESS_KEY)).isEqualTo(HOST);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,144 @@
/*
* 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.registry;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
/**
* Test for {@link PolarisAutoServiceRegistration}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisAutoServiceRegistrationTest {
@Mock
private ServiceRegistry<Registration> serviceRegistry;
@Mock
private AutoServiceRegistrationProperties autoServiceRegistrationProperties;
@Mock
private PolarisDiscoveryProperties polarisDiscoveryProperties;
@Mock
private ApplicationContext applicationContext;
@Mock
private Environment environment;
@Mock
private PolarisRegistration registration;
private PolarisAutoServiceRegistration polarisAutoServiceRegistration;
@Before
public void setUp() {
doReturn(polarisDiscoveryProperties).when(registration).getPolarisProperties();
doNothing().when(serviceRegistry).register(nullable(Registration.class));
polarisAutoServiceRegistration =
new PolarisAutoServiceRegistration(serviceRegistry, autoServiceRegistrationProperties, registration);
doReturn(environment).when(applicationContext).getEnvironment();
polarisAutoServiceRegistration.setApplicationContext(applicationContext);
}
@Test
public void testRegister() {
doReturn(false).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
doReturn(true).when(registration).isRegisterEnabled();
doReturn(-1).when(registration).getPort();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
doReturn(PORT).when(registration).getPort();
try {
polarisAutoServiceRegistration.register();
}
catch (Exception e) {
fail();
}
}
@Test
public void testGetManagementRegistration() {
assertThat(polarisAutoServiceRegistration.getManagementRegistration()).isNull();
}
@Test
public void testRegisterManagement() {
doReturn(false).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.registerManagement();
}
catch (Exception e) {
fail();
}
doReturn(true).when(registration).isRegisterEnabled();
try {
polarisAutoServiceRegistration.registerManagement();
}
catch (Exception e) {
fail();
}
}
@Test
public void testGetAppName() {
doReturn("application").when(environment).getProperty(anyString(), anyString());
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo("application");
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
assertThat(polarisAutoServiceRegistration.getAppName()).isEqualTo(SERVICE_PROVIDER);
}
}

@ -0,0 +1,133 @@
/*
* 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.registry;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.polaris.DiscoveryPropertiesAutoConfiguration;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.polaris.api.config.Configuration;
import com.tencent.polaris.api.config.global.APIConfig;
import com.tencent.polaris.api.config.global.GlobalConfig;
import com.tencent.polaris.client.api.SDKContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static com.tencent.polaris.test.common.Consts.HOST;
import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Test for {@link PolarisRegistration}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisRegistrationTest {
private PolarisRegistration polarisRegistration;
@Before
public void setUp() {
// mock DiscoveryPropertiesAutoConfiguration
DiscoveryPropertiesAutoConfiguration discoveryPropertiesAutoConfiguration =
mock(DiscoveryPropertiesAutoConfiguration.class);
doReturn(true).when(discoveryPropertiesAutoConfiguration).isRegisterEnabled();
// mock PolarisDiscoveryProperties
PolarisDiscoveryProperties polarisDiscoveryProperties = mock(PolarisDiscoveryProperties.class);
doReturn(SERVICE_PROVIDER).when(polarisDiscoveryProperties).getService();
doReturn(PORT).when(polarisDiscoveryProperties).getPort();
doReturn("http").when(polarisDiscoveryProperties).getProtocol();
// mock SDKContext
APIConfig apiConfig = mock(APIConfig.class);
doReturn(HOST).when(apiConfig).getBindIP();
GlobalConfig globalConfig = mock(GlobalConfig.class);
doReturn(apiConfig).when(globalConfig).getAPI();
Configuration configuration = mock(Configuration.class);
doReturn(globalConfig).when(configuration).getGlobal();
SDKContext polarisContext = mock(SDKContext.class);
doReturn(configuration).when(polarisContext).getConfig();
// mock StaticMetadataManager
StaticMetadataManager staticMetadataManager = mock(StaticMetadataManager.class);
doReturn(Collections.singletonMap("key1", "value1")).when(staticMetadataManager).getMergedStaticMetadata();
doReturn(Collections.singletonMap("key2", "value2")).when(staticMetadataManager).getLocationMetadata();
polarisRegistration = new PolarisRegistration(
discoveryPropertiesAutoConfiguration, polarisDiscoveryProperties, polarisContext, staticMetadataManager);
}
@Test
public void testGetServiceId() {
assertThat(polarisRegistration.getServiceId()).isEqualTo(SERVICE_PROVIDER);
}
@Test
public void testGetHost() {
assertThat(polarisRegistration.getHost()).isEqualTo(HOST);
}
@Test
public void testGetPort() {
assertThat(polarisRegistration.getPort()).isEqualTo(PORT);
}
@Test
public void testIsSecure() {
assertThat(polarisRegistration.isSecure()).isFalse();
}
@Test
public void testGetUri() {
assertThat(polarisRegistration.getUri().toString()).isEqualTo("http://" + HOST + ":" + PORT);
}
@Test
public void testGetMetadata() {
Map<String, String> metadata = polarisRegistration.getMetadata();
assertThat(metadata).isNotNull();
assertThat(metadata).isNotEmpty();
assertThat(metadata.size()).isEqualTo(2);
assertThat(metadata.get("key1")).isEqualTo("value1");
assertThat(metadata.get("key2")).isEqualTo("value2");
}
@Test
public void testGetPolarisProperties() {
assertThat(polarisRegistration.getPolarisProperties()).isNotNull();
}
@Test
public void testIsRegisterEnabled() {
assertThat(polarisRegistration.isRegisterEnabled()).isTrue();
}
@Test
public void testToString() {
System.out.println(polarisRegistration);
}
}

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

@ -30,7 +30,6 @@ import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Configuration;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
@ -38,10 +37,11 @@ import static com.tencent.polaris.test.common.Consts.PORT;
import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
/**
* Test for {@link PolarisServiceRegistry}
* Test for {@link PolarisServiceRegistry}.
*
* @author Haotian Zhang
*/
@ -105,9 +105,24 @@ public class PolarisServiceRegistryTest {
});
}
@Test
public void testDeRegister() {
this.contextRunner.run(context -> {
PolarisServiceRegistry registry = context
.getBean(PolarisServiceRegistry.class);
PolarisRegistration registration = Mockito.mock(PolarisRegistration.class);
doReturn(null).when(registration).getServiceId();
try {
registry.deregister(registration);
}
catch (Throwable throwable) {
fail();
}
});
}
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
static class PolarisPropertiesConfiguration {
}

@ -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.util;
import org.assertj.core.util.Maps;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link OkHttpUtil}.
*
* @author Haotian Zhang
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = OkHttpUtilTest.TestApplication.class)
public class OkHttpUtilTest {
@LocalServerPort
private int port;
@Test
public void testGet() {
assertThat(OkHttpUtil.get("http://localhost:" + port + "/test", Maps.newHashMap("key", "value"))).isTrue();
assertThat(OkHttpUtil.get("http://localhost:" + port + "/error", Maps.newHashMap("key", "value"))).isFalse();
assertThat(OkHttpUtil.get("http://localhost:55555/error", Maps.newHashMap("key", "value"))).isFalse();
}
@SpringBootApplication
@RestController
static class TestApplication {
@GetMapping("/test")
public String test() {
return "test";
}
}
}

@ -0,0 +1,24 @@
server:
port: 48084
spring:
application:
name: java_provider_test
cloud:
polaris:
address: grpc://127.0.0.1:8091
namespace: Test
enabled: true
discovery:
enabled: true
register: true
consul:
port: 8500
host: 127.0.0.1
enabled: true
discovery:
enabled: true
register: true
instance-id: ins-test
service-name: ${spring.application.name}
ip-address: 127.0.0.1
prefer-ip-address: true

@ -34,10 +34,6 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-canary</artifactId>
@ -89,14 +85,20 @@
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

@ -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;
}
}

@ -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.ratelimit.config;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Autoconfiguration of rate limit at bootstrap phase.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class PolarisRateLimitBootstrapConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean
public RateLimitConfigModifier rateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
return new RateLimitConfigModifier(polarisRateLimitProperties);
}
/**
* Config modifier for rate limit.
*
* @author Haotian Zhang
*/
public static class RateLimitConfigModifier implements PolarisConfigModifier {
private PolarisRateLimitProperties polarisRateLimitProperties;
public RateLimitConfigModifier(PolarisRateLimitProperties polarisRateLimitProperties) {
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@Override
public void modify(ConfigurationImpl configuration) {
// Update MaxQueuingTime.
configuration.getProvider().getRateLimit()
.setMaxQueuingTime(polarisRateLimitProperties.getMaxQueuingTime());
}
@Override
public int getOrder() {
return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER;
}
}
}

@ -13,11 +13,15 @@
* 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.config;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
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.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
@ -27,6 +31,7 @@ import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.factory.LimitAPIFactory;
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.ConditionalOnWebApplication;
@ -42,17 +47,15 @@ import static javax.servlet.DispatcherType.INCLUDE;
import static javax.servlet.DispatcherType.REQUEST;
/**
* Configuration of rate limit.
*
* @author Haotian Zhang
*/
@Configuration
@ConditionalOnPolarisEnabled
@AutoConfigureAfter(PolarisContextAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class RateLimitConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
public class PolarisRateLimitConfiguration {
@Bean
@ConditionalOnMissingBean
@ -60,6 +63,11 @@ public class RateLimitConfiguration {
return LimitAPIFactory.createLimitAPIByContext(polarisContext);
}
@Bean
public RateLimitRuleLabelResolver rateLimitRuleLabelService(ServiceRuleManager serviceRuleManager) {
return new RateLimitRuleLabelResolver(serviceRuleManager);
}
/**
* Create when web application type is SERVLET.
*/
@ -71,9 +79,10 @@ public class RateLimitConfiguration {
@ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckServletFilter(limitAPI, labelResolver,
polarisRateLimitProperties);
polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Bean
@ -99,9 +108,10 @@ public class RateLimitConfiguration {
@Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
return new QuotaCheckReactiveFilter(limitAPI, labelResolver,
polarisRateLimitProperties);
polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
}

@ -44,6 +44,11 @@ public class PolarisRateLimitProperties {
*/
private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value();
/**
* Max queuing time when using unirate.
*/
private long maxQueuingTime = 1000L;
public String getRejectRequestTips() {
return rejectRequestTips;
}
@ -68,4 +73,11 @@ public class PolarisRateLimitProperties {
this.rejectHttpCode = rejectHttpCode;
}
public long getMaxQueuingTime() {
return maxQueuingTime;
}
public void setMaxQueuingTime(long maxQueuingTime) {
this.maxQueuingTime = maxQueuingTime;
}
}

@ -21,10 +21,14 @@ package com.tencent.cloud.polaris.ratelimit.filter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import com.google.common.collect.Maps;
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.constant.RateLimitConstant;
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.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
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.
*
* @author Haotian Zhang
* @author Haotian Zhang, lepdou
*/
public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
@ -64,14 +67,18 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI;
this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
}
@PostConstruct
@ -89,30 +96,12 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE;
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 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);
}
}
Map<String, String> labels = getRequestLabels(exchange, localNamespace, localService);
try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels,
null);
String path = exchange.getRequest().getURI().getPath();
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, path);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse();
@ -122,6 +111,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
.write(rejectTips.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
}
}
catch (Throwable t) {
// An exception occurs in the rate limiting API call,
@ -132,4 +125,41 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
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;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
@ -29,6 +31,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
@ -42,7 +46,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD;
@ -50,7 +53,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB
/**
* Servlet filter to check quota.
*
* @author Haotian Zhang
* @author Haotian Zhang, lepdou
*/
@Order(RateLimitConstant.FILTER_ORDER)
public class QuotaCheckServletFilter extends OncePerRequestFilter {
@ -63,14 +66,18 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private final PolarisRateLimitProperties polarisRateLimitProperties;
private final RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
PolarisRateLimitProperties polarisRateLimitProperties,
RateLimitRuleLabelResolver rateLimitRuleLabelResolver) {
this.limitAPI = limitAPI;
this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
this.rateLimitRuleLabelResolver = rateLimitRuleLabelResolver;
}
@PostConstruct
@ -84,38 +91,23 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
String localNamespace = MetadataContext.LOCAL_NAMESPACE;
String localService = MetadataContext.LOCAL_SERVICE;
Map<String, String> labels = new HashMap<>();
// add build in labels
String path = request.getRequestURI();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// add custom labels
if (labelResolver != null) {
try {
Map<String, String> customLabels = labelResolver.resolve(request);
if (!CollectionUtils.isEmpty(customLabels)) {
labels.putAll(customLabels);
}
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
}
}
Map<String, String> labels = getRequestLabels(request, localNamespace, localService);
try {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels,
null);
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, request.getRequestURI());
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(rejectTips);
return;
}
else {
filterChain.doFilter(request, response);
// Unirate
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) {
Thread.sleep(quotaResponse.getWaitMs());
}
filterChain.doFilter(request, response);
}
catch (Throwable t) {
// An exception occurs in the rate limiting API call,
@ -125,4 +117,41 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
}
}
private Map<String, String> getRequestLabels(HttpServletRequest request, String localNamespace, String localService) {
Map<String, String> labels = new HashMap<>();
// add build in labels
String path = request.getRequestURI();
if (StringUtils.isNotBlank(path)) {
labels.put(LABEL_METHOD, path);
}
// 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) {
try {
return labelResolver.resolve(request);
}
catch (Throwable e) {
LOG.error("resolve custom label failed. resolver = {}",
labelResolver.getClass().getName(), e);
}
}
return Collections.emptyMap();
}
private Map<String, String> getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) {
Set<String> expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service);
return ExpressionLabelUtils.resolve(request, expressionLabels);
}
}

@ -39,16 +39,15 @@ public final class RateLimitUtils {
}
public static String getRejectTips(
PolarisRateLimitProperties polarisRateLimitProperties) {
public static String getRejectTips(PolarisRateLimitProperties polarisRateLimitProperties) {
String tips = polarisRateLimitProperties.getRejectRequestTips();
if (!StringUtils.isEmpty(tips)) {
if (StringUtils.hasText(tips)) {
return tips;
}
String rejectFilePath = polarisRateLimitProperties.getRejectRequestTipsFilePath();
if (!StringUtils.isEmpty(rejectFilePath)) {
if (StringUtils.hasText(rejectFilePath)) {
try {
tips = ResourceFileUtils.readFile(rejectFilePath);
}
@ -58,7 +57,7 @@ public final class RateLimitUtils {
}
}
if (!StringUtils.isEmpty(tips)) {
if (StringUtils.hasText(tips)) {
return tips;
}

@ -23,6 +23,12 @@
"type": "java.lang.Integer",
"defaultValue": "429",
"description": "Custom http code when reject request."
},
{
"name": "spring.cloud.polaris.ratelimit.maxQueuingTime",
"type": "java.lang.Long",
"defaultValue": "1000",
"description": "Max queuing time when using unirate."
}
]
}

@ -1,2 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.RateLimitConfiguration
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitBootstrapConfiguration

@ -0,0 +1,101 @@
/*
* 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.Set;
import com.google.protobuf.StringValue;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.polaris.client.pb.ModelProto;
import com.tencent.polaris.client.pb.RateLimitProto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link RateLimitRuleLabelResolver}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class RateLimitRuleLabelResolverTest {
private ServiceRuleManager serviceRuleManager;
private RateLimitRuleLabelResolver rateLimitRuleLabelResolver;
@Before
public void setUp() {
serviceRuleManager = mock(ServiceRuleManager.class);
when(serviceRuleManager.getServiceRateLimitRule(any(), anyString())).thenAnswer(invocationOnMock -> {
String serviceName = invocationOnMock.getArgument(1).toString();
if (serviceName.equals("TestApp1")) {
return null;
}
else if (serviceName.equals("TestApp2")) {
return RateLimitProto.RateLimit.newBuilder().build();
}
else if (serviceName.equals("TestApp3")) {
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder().build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
else {
ModelProto.MatchString matchString = ModelProto.MatchString.newBuilder()
.setType(ModelProto.MatchString.MatchStringType.EXACT)
.setValue(StringValue.of("value"))
.setValueType(ModelProto.MatchString.ValueType.TEXT).build();
RateLimitProto.Rule rule = RateLimitProto.Rule.newBuilder()
.putLabels("${http.method}", matchString).build();
return RateLimitProto.RateLimit.newBuilder().addRules(rule).build();
}
});
rateLimitRuleLabelResolver = new RateLimitRuleLabelResolver(serviceRuleManager);
}
@Test
public void testGetExpressionLabelKeys() {
// rateLimitRule == null
String serviceName = "TestApp1";
Set<String> labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(rules)
serviceName = "TestApp2";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// CollectionUtils.isEmpty(labels)
serviceName = "TestApp3";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isEmpty();
// Has labels
serviceName = "TestApp4";
labelKeys = rateLimitRuleLabelResolver.getExpressionLabelKeys(null, serviceName);
assertThat(labelKeys).isNotEmpty();
assertThat(labelKeys).contains("${http.method}");
}
}

@ -0,0 +1,46 @@
/*
* 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.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitBootstrapConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitBootstrapConfigurationTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PolarisRateLimitBootstrapConfiguration.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.enabled=true");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisRateLimitProperties.class);
assertThat(context).hasSingleBean(PolarisRateLimitBootstrapConfiguration.RateLimitConfigModifier.class);
});
}
}

@ -0,0 +1,99 @@
/*
* 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.config;
import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckReactiveFilter;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitConfiguration}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitConfigurationTest {
private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner();
private WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner();
private ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner();
@Test
public void testNoWebApplication() {
this.applicationContextRunner
.withConfiguration(AutoConfigurations.of(
PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testServletWebApplication() {
this.webApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckServletFilter.class);
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckReactiveFilter.class);
});
}
@Test
public void testReactiveWebApplication() {
this.reactiveWebApplicationContextRunner
.withConfiguration(AutoConfigurations.of(PolarisContextAutoConfiguration.class,
PolarisRateLimitProperties.class,
PolarisRateLimitConfiguration.class))
.run(context -> {
assertThat(context).hasSingleBean(LimitAPI.class);
assertThat(context).hasSingleBean(RateLimitRuleLabelResolver.class);
assertThat(context).doesNotHaveBean(PolarisRateLimitConfiguration.QuotaCheckFilterConfig.class);
assertThat(context).doesNotHaveBean(QuotaCheckServletFilter.class);
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).hasSingleBean(PolarisRateLimitConfiguration.MetadataReactiveFilterConfig.class);
assertThat(context).hasSingleBean(QuotaCheckReactiveFilter.class);
});
}
}

@ -0,0 +1,59 @@
/*
* 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.config;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisRateLimitProperties}.
*
* @author Haotian Zhang
*/
public class PolarisRateLimitPropertiesTest {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisRateLimitPropertiesAutoConfiguration.class, PolarisRateLimitProperties.class))
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTips=xxx")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectRequestTipsFilePath=/index.html")
.withPropertyValues("spring.cloud.polaris.ratelimit.rejectHttpCode=419")
.withPropertyValues("spring.cloud.polaris.ratelimit.maxQueuingTime=500");
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {
PolarisRateLimitProperties polarisRateLimitProperties = context.getBean(PolarisRateLimitProperties.class);
assertThat(polarisRateLimitProperties.getRejectRequestTips()).isEqualTo("xxx");
assertThat(polarisRateLimitProperties.getRejectRequestTipsFilePath()).isEqualTo("/index.html");
assertThat(polarisRateLimitProperties.getRejectHttpCode()).isEqualTo(419);
assertThat(polarisRateLimitProperties.getMaxQueuingTime()).isEqualTo(500L);
});
}
@Configuration
@EnableAutoConfiguration
static class PolarisRateLimitPropertiesAutoConfiguration {
}
}

@ -0,0 +1,223 @@
/*
* 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.filter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
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.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckReactiveFilter}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class QuotaCheckReactiveFilterTest {
private PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver");
private QuotaCheckReactiveFilter quotaCheckReactiveFilter;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterClass
public static void afterClass() {
mockedApplicationContextAwareUtils.close();
expressionLabelUtilsMockedStatic.close();
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testGetOrder() {
assertThat(this.quotaCheckReactiveFilter.getOrder()).isEqualTo(RateLimitConstant.FILTER_ORDER);
}
@Test
public void testInit() {
quotaCheckReactiveFilter.init();
try {
Field rejectTips = QuotaCheckReactiveFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckReactiveFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckReactiveFilter.class.getDeclaredMethod("getCustomResolvedLabels", ServerWebExchange.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ReactiveResolver")).isEqualTo("ReactiveResolver");
// throw exception
PolarisRateLimiterLabelReactiveResolver exceptionLabelResolver = new PolarisRateLimiterLabelReactiveResolver() {
@Override
public Map<String, String> resolve(ServerWebExchange exchange) {
throw new RuntimeException("Mock exception.");
}
};
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckReactiveFilter = new QuotaCheckReactiveFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckReactiveFilter, exchange);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testFilter() {
// Create mock WebFilterChain
WebFilterChain webFilterChain = serverWebExchange -> Mono.empty();
// Mock request
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/test").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
quotaCheckReactiveFilter.init();
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
ServerHttpResponse response = exchange.getResponse();
assertThat(response.getRawStatusCode()).isEqualTo(419);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE);
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckReactiveFilter.filter(exchange, webFilterChain);
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,220 @@
/*
* 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.filter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
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.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckServletFilter}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest(classes = QuotaCheckServletFilterTest.TestApplication.class, properties = {
"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"
})
public class QuotaCheckServletFilterTest {
private PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver");
private QuotaCheckServletFilter quotaCheckServletFilter;
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static MockedStatic<ExpressionLabelUtils> expressionLabelUtilsMockedStatic;
@BeforeClass
public static void beforeClass() {
expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class);
when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())).thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver"));
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties(anyString()))
.thenReturn("unit-test");
}
@AfterClass
public static void afterClass() throws Exception {
mockedApplicationContextAwareUtils.close();
expressionLabelUtilsMockedStatic.close();
}
@Before
public void setUp() {
MetadataContext.LOCAL_NAMESPACE = "TEST";
LimitAPI limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
return new QuotaResponse(new QuotaResult(null, 0, null));
}
});
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
polarisRateLimitProperties.setRejectHttpCode(419);
RateLimitRuleLabelResolver rateLimitRuleLabelResolver = mock(RateLimitRuleLabelResolver.class);
when(rateLimitRuleLabelResolver.getExpressionLabelKeys(anyString(), anyString())).thenReturn(Collections.EMPTY_SET);
this.quotaCheckServletFilter = new QuotaCheckServletFilter(limitAPI, labelResolver, polarisRateLimitProperties, rateLimitRuleLabelResolver);
}
@Test
public void testInit() {
quotaCheckServletFilter.init();
try {
Field rejectTips = QuotaCheckServletFilter.class.getDeclaredField("rejectTips");
rejectTips.setAccessible(true);
assertThat(rejectTips.get(quotaCheckServletFilter)).isEqualTo("RejectRequestTips");
}
catch (NoSuchFieldException | IllegalAccessException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testGetRuleExpressionLabels() {
try {
Method getCustomResolvedLabels = QuotaCheckServletFilter.class.getDeclaredMethod("getCustomResolvedLabels", HttpServletRequest.class);
getCustomResolvedLabels.setAccessible(true);
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
// labelResolver != null
Map<String, String> result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get("ServletResolver")).isEqualTo("ServletResolver");
// throw exception
PolarisRateLimiterLabelServletResolver exceptionLabelResolver = request1 -> {
throw new RuntimeException("Mock exception.");
};
quotaCheckServletFilter = new QuotaCheckServletFilter(null, exceptionLabelResolver, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
// labelResolver == null
quotaCheckServletFilter = new QuotaCheckServletFilter(null, null, null, null);
result = (Map<String, String>) getCustomResolvedLabels.invoke(quotaCheckServletFilter, request);
assertThat(result.size()).isEqualTo(0);
getCustomResolvedLabels.setAccessible(false);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
fail("Exception encountered.", e);
}
}
@Test
public void testDoFilterInternal() {
// Create mock FilterChain
FilterChain filterChain = (servletRequest, servletResponse) -> {
};
// Mock request
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
quotaCheckServletFilter.init();
try {
// Pass
MetadataContext.LOCAL_SERVICE = "TestApp1";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
// Unirate waiting 1000ms
MetadataContext.LOCAL_SERVICE = "TestApp2";
long startTimestamp = System.currentTimeMillis();
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L);
// Rate limited
MetadataContext.LOCAL_SERVICE = "TestApp3";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(419);
assertThat(response.getContentAsString()).isEqualTo("RejectRequestTips");
// Exception
MetadataContext.LOCAL_SERVICE = "TestApp4";
quotaCheckServletFilter.doFilterInternal(request, response, filterChain);
}
catch (ServletException | IOException e) {
fail("Exception encountered.", e);
}
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -0,0 +1,95 @@
/*
* 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.utils;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link QuotaCheckUtils}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class QuotaCheckUtilsTest {
private LimitAPI limitAPI;
@Before
public void setUp() {
limitAPI = mock(LimitAPI.class);
when(limitAPI.getQuota(any(QuotaRequest.class))).thenAnswer(invocationOnMock -> {
String serviceName = ((QuotaRequest) invocationOnMock.getArgument(0)).getService();
if (serviceName.equals("TestApp1")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp2")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultOk, 1000, "QuotaResultOk"));
}
else if (serviceName.equals("TestApp3")) {
return new QuotaResponse(new QuotaResult(QuotaResult.Code.QuotaResultLimited, 0, "QuotaResultLimited"));
}
else {
throw new RuntimeException("Mock exception.");
}
});
}
@Test
public void testGetQuota() {
// Pass
String serviceName = "TestApp1";
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Unirate waiting 1000ms
serviceName = "TestApp2";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(1000);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultOk");
// Rate limited
serviceName = "TestApp3";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultLimited);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("QuotaResultLimited");
// Exception
serviceName = "TestApp4";
quotaResponse = QuotaCheckUtils.getQuota(limitAPI, null, serviceName, 1, null, null);
assertThat(quotaResponse.getCode()).isEqualTo(QuotaResultCode.QuotaResultOk);
assertThat(quotaResponse.getWaitMs()).isEqualTo(0);
assertThat(quotaResponse.getInfo()).isEqualTo("get quota failed");
}
}

@ -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.ratelimit.utils;
import java.io.IOException;
import com.tencent.cloud.common.util.ResourceFileUtils;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.QUOTA_LIMITED_INFO;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* Test for {@link RateLimitUtils}.
*
* @author Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class RateLimitUtilsTest {
@BeforeClass
public static void beforeClass() throws IOException {
mockStatic(ResourceFileUtils.class);
when(ResourceFileUtils.readFile(anyString())).thenAnswer(invocation -> {
String rejectFilePath = invocation.getArgument(0).toString();
if (rejectFilePath.equals("exception.html")) {
throw new IOException("Mock exceptions");
}
else {
return "RejectRequestTips";
}
});
}
@Test
public void testGetRejectTips() {
PolarisRateLimitProperties polarisRateLimitProperties = new PolarisRateLimitProperties();
// RejectRequestTips
polarisRateLimitProperties.setRejectRequestTips("RejectRequestTips");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("reject-tips.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo("RejectRequestTips");
// RejectRequestTipsFilePath with Exception
polarisRateLimitProperties.setRejectRequestTips(null);
polarisRateLimitProperties.setRejectRequestTipsFilePath("exception.html");
assertThat(RateLimitUtils.getRejectTips(polarisRateLimitProperties)).isEqualTo(QUOTA_LIMITED_INFO);
}
}

@ -66,20 +66,26 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<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>

@ -13,7 +13,6 @@
* 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.constant;

@ -24,6 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
@ -34,6 +36,11 @@ import org.springframework.util.StringUtils;
*/
public class MetadataContext {
/**
* transitive context.
*/
public static final String FRAGMENT_TRANSITIVE = "transitive";
private static final Logger LOG = LoggerFactory.getLogger(MetadataContext.class);
/**
* Namespace of local instance.
*/
@ -51,6 +58,14 @@ public class MetadataContext {
namespace = ApplicationContextAwareUtils
.getProperties("spring.cloud.polaris.discovery.namespace", "default");
}
if (!StringUtils.hasText(namespace)) {
LOG.error("namespace should not be blank. please configure spring.cloud.polaris.namespace or "
+ "spring.cloud.polaris.discovery.namespace");
throw new RuntimeException("namespace should not be blank. please configure spring.cloud.polaris.namespace or "
+ "spring.cloud.polaris.discovery.namespace");
}
LOCAL_NAMESPACE = namespace;
String serviceName = ApplicationContextAwareUtils
@ -60,38 +75,56 @@ public class MetadataContext {
"spring.cloud.polaris.discovery.service", ApplicationContextAwareUtils
.getProperties("spring.application.name", null));
}
if (!StringUtils.hasText(serviceName)) {
LOG.error("service name should not be blank. please configure spring.cloud.polaris.service or "
+ "spring.cloud.polaris.discovery.service or spring.application.name");
throw new RuntimeException("service name should not be blank. please configure spring.cloud.polaris.service or "
+ "spring.cloud.polaris.discovery.service or spring.application.name");
}
LOCAL_SERVICE = serviceName;
}
/**
* Transitive custom metadata content.
*/
private final Map<String, String> transitiveCustomMetadata;
private final Map<String, Map<String, String>> fragmentContexts;
public MetadataContext() {
this.transitiveCustomMetadata = new ConcurrentHashMap<>();
this.fragmentContexts = new ConcurrentHashMap<>();
}
public Map<String, String> getAllTransitiveCustomMetadata() {
return Collections.unmodifiableMap(this.transitiveCustomMetadata);
public Map<String, String> getFragmentContext(String fragment) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(fragmentContext);
}
public String getTransitiveCustomMetadata(String key) {
return this.transitiveCustomMetadata.get(key);
public String getContext(String fragment, String key) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
return null;
}
return fragmentContext.get(key);
}
public void putTransitiveCustomMetadata(String key, String value) {
this.transitiveCustomMetadata.put(key, value);
public void putContext(String fragment, String key, String value) {
Map<String, String> fragmentContext = fragmentContexts.get(fragment);
if (fragmentContext == null) {
fragmentContext = new ConcurrentHashMap<>();
fragmentContexts.put(fragment, fragmentContext);
}
fragmentContext.put(key, value);
}
public void putAllTransitiveCustomMetadata(Map<String, String> customMetadata) {
this.transitiveCustomMetadata.putAll(customMetadata);
public void putFragmentContext(String fragment, Map<String, String> context) {
fragmentContexts.put(fragment, context);
}
@Override
public String toString() {
return "MetadataContext{" + "transitiveCustomMetadata="
+ JacksonUtils.serialize2Json(transitiveCustomMetadata) + '}';
return "MetadataContext{" +
"fragmentContexts=" + JacksonUtils.serialize2Json(fragmentContexts) +
'}';
}
}

@ -17,8 +17,8 @@
package com.tencent.cloud.common.metadata;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
@ -36,9 +36,9 @@ public final class MetadataContextHolder {
private static final ThreadLocal<MetadataContext> METADATA_CONTEXT = new InheritableThreadLocal<>();
private static MetadataLocalProperties metadataLocalProperties;
private static StaticMetadataManager staticMetadataManager;
private MetadataContextHolder() {
}
/**
@ -46,39 +46,27 @@ public final class MetadataContextHolder {
* @return METADATA_CONTEXT
*/
public static MetadataContext get() {
if (null == METADATA_CONTEXT.get()) {
MetadataContext metadataContext = new MetadataContext();
if (metadataLocalProperties == null) {
metadataLocalProperties = (MetadataLocalProperties) ApplicationContextAwareUtils
.getApplicationContext().getBean("metadataLocalProperties");
}
// init custom metadata and load local metadata
Map<String, String> transitiveMetadataMap = getTransitiveMetadataMap(
metadataLocalProperties.getContent(),
metadataLocalProperties.getTransitive());
metadataContext.putAllTransitiveCustomMetadata(transitiveMetadataMap);
METADATA_CONTEXT.set(metadataContext);
if (METADATA_CONTEXT.get() != null) {
return METADATA_CONTEXT.get();
}
return METADATA_CONTEXT.get();
}
/**
* Filter and store the transitive metadata to transitive metadata context.
* @param source all metadata content
* @param transitiveMetadataKeyList transitive metadata name list
* @return result
*/
private static Map<String, String> getTransitiveMetadataMap(
Map<String, String> source, List<String> transitiveMetadataKeyList) {
Map<String, String> result = new HashMap<>();
for (String key : transitiveMetadataKeyList) {
if (source.containsKey(key)) {
result.put(key, source.get(key));
}
if (metadataLocalProperties == null) {
metadataLocalProperties = (MetadataLocalProperties) ApplicationContextAwareUtils
.getApplicationContext().getBean("metadataLocalProperties");
}
if (staticMetadataManager == null) {
staticMetadataManager = (StaticMetadataManager) ApplicationContextAwareUtils
.getApplicationContext().getBean("metadataManager");
}
return result;
// init static transitive metadata
MetadataContext metadataContext = new MetadataContext();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE,
staticMetadataManager.getMergedStaticTransitiveMetadata());
METADATA_CONTEXT.set(metadataContext);
return METADATA_CONTEXT.get();
}
/**
@ -91,16 +79,22 @@ public final class MetadataContextHolder {
/**
* Save metadata map to thread local.
* @param customMetadataMap custom metadata collection
* @param dynamicTransitiveMetadata custom metadata collection
*/
public static void init(Map<String, String> customMetadataMap) {
public static void init(Map<String, String> dynamicTransitiveMetadata) {
// Init ThreadLocal.
MetadataContextHolder.remove();
MetadataContext metadataContext = MetadataContextHolder.get();
// Save to ThreadLocal.
if (!CollectionUtils.isEmpty(customMetadataMap)) {
metadataContext.putAllTransitiveCustomMetadata(customMetadataMap);
// Save transitive metadata to ThreadLocal.
if (!CollectionUtils.isEmpty(dynamicTransitiveMetadata)) {
Map<String, String> staticTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(staticTransitiveMetadata);
mergedTransitiveMetadata.putAll(dynamicTransitiveMetadata);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE,
Collections.unmodifiableMap(mergedTransitiveMetadata));
}
MetadataContextHolder.set(metadataContext);
}

@ -0,0 +1,205 @@
/*
* 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;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* manage metadata from env/config file.
*
*@author lepdou 2022-05-20
*/
public class StaticMetadataManager {
private static final Logger LOGGER = LoggerFactory.getLogger(StaticMetadataManager.class);
private static final String ENV_METADATA_PREFIX = "SCT_METADATA_CONTENT_";
private static final int ENV_METADATA_PREFIX_LENGTH = ENV_METADATA_PREFIX.length();
private static final String ENV_METADATA_CONTENT_TRANSITIVE = "SCT_METADATA_CONTENT_TRANSITIVE";
private static final String ENV_METADATA_ZONE = "SCT_METADATA_ZONE";
private static final String ENV_METADATA_REGION = "SCT_METADATA_REGION";
private static final String ENV_METADATA_CAMPUS = "SCT_METADATA_CAMPUS";
private static final String LOCATION_KEY_REGION = "region";
private static final String LOCATION_KEY_ZONE = "zone";
private static final String LOCATION_KEY_CAMPUS = "campus";
private Map<String, String> envMetadata;
private Map<String, String> envTransitiveMetadata;
private Map<String, String> configMetadata;
private Map<String, String> configTransitiveMetadata;
private Map<String, String> mergedStaticMetadata;
private Map<String, String> mergedStaticTransitiveMetadata;
private String zone;
private String region;
private String campus;
public StaticMetadataManager(MetadataLocalProperties metadataLocalProperties) {
parseConfigMetadata(metadataLocalProperties);
parseEnvMetadata();
merge();
parseLocationMetadata();
LOGGER.info("[SCT] Loaded static metadata info. {}", this);
}
private void parseEnvMetadata() {
Map<String, String> allEnvs = System.getenv();
envMetadata = new HashMap<>();
// parse all metadata
for (Map.Entry<String, String> entry : allEnvs.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (StringUtils.isNotBlank(key) && key.startsWith(ENV_METADATA_PREFIX)
&& !key.equals(ENV_METADATA_CONTENT_TRANSITIVE)) {
String sourceKey = StringUtils.substring(key, ENV_METADATA_PREFIX_LENGTH);
envMetadata.put(sourceKey, value);
LOGGER.info("[SCT] resolve metadata from env. key = {}, value = {}", sourceKey, value);
}
}
envMetadata = Collections.unmodifiableMap(envMetadata);
envTransitiveMetadata = new HashMap<>();
// parse transitive metadata
String transitiveKeys = allEnvs.get(ENV_METADATA_CONTENT_TRANSITIVE);
if (StringUtils.isNotBlank(transitiveKeys)) {
String[] keyArr = StringUtils.split(transitiveKeys, ",");
if (keyArr != null && keyArr.length > 0) {
for (String key : keyArr) {
String value = envMetadata.get(key);
if (StringUtils.isNotBlank(value)) {
envTransitiveMetadata.put(key, value);
}
}
}
}
envTransitiveMetadata = Collections.unmodifiableMap(envTransitiveMetadata);
}
private void parseConfigMetadata(MetadataLocalProperties metadataLocalProperties) {
Map<String, String> allMetadata = metadataLocalProperties.getContent();
List<String> transitiveKeys = metadataLocalProperties.getTransitive();
Map<String, String> result = new HashMap<>();
for (String key : transitiveKeys) {
if (allMetadata.containsKey(key)) {
result.put(key, allMetadata.get(key));
}
}
configTransitiveMetadata = Collections.unmodifiableMap(result);
configMetadata = Collections.unmodifiableMap(allMetadata);
}
private void merge() {
// env priority is bigger than config
Map<String, String> mergedMetadataResult = new HashMap<>();
mergedMetadataResult.putAll(configMetadata);
mergedMetadataResult.putAll(envMetadata);
this.mergedStaticMetadata = Collections.unmodifiableMap(mergedMetadataResult);
Map<String, String> mergedTransitiveMetadataResult = new HashMap<>();
mergedTransitiveMetadataResult.putAll(configTransitiveMetadata);
mergedTransitiveMetadataResult.putAll(envTransitiveMetadata);
this.mergedStaticTransitiveMetadata = Collections.unmodifiableMap(mergedTransitiveMetadataResult);
}
private void parseLocationMetadata() {
zone = System.getenv(ENV_METADATA_ZONE);
region = System.getenv(ENV_METADATA_REGION);
campus = System.getenv(ENV_METADATA_CAMPUS);
}
public Map<String, String> getAllEnvMetadata() {
return envMetadata;
}
public Map<String, String> getEnvTransitiveMetadata() {
return envTransitiveMetadata;
}
public Map<String, String> getAllConfigMetadata() {
return configMetadata;
}
public Map<String, String> getConfigTransitiveMetadata() {
return configTransitiveMetadata;
}
public Map<String, String> getMergedStaticMetadata() {
return mergedStaticMetadata;
}
public Map<String, String> getMergedStaticTransitiveMetadata() {
return mergedStaticTransitiveMetadata;
}
public String getZone() {
return zone;
}
public String getRegion() {
return region;
}
public String getCampus() {
return campus;
}
public Map<String, String> getLocationMetadata() {
Map<String, String> locationMetadata = new HashMap<>();
if (StringUtils.isNotBlank(region)) {
locationMetadata.put(LOCATION_KEY_REGION, region);
}
if (StringUtils.isNotBlank(zone)) {
locationMetadata.put(LOCATION_KEY_ZONE, zone);
}
if (StringUtils.isNotBlank(campus)) {
locationMetadata.put(LOCATION_KEY_CAMPUS, campus);
}
return locationMetadata;
}
@Override
public String toString() {
return "StaticMetadataManager{" +
"envMetadata=" + envMetadata +
", envTransitiveMetadata=" + envTransitiveMetadata +
", configMetadata=" + configMetadata +
", configTransitiveMetadata=" + configTransitiveMetadata +
", mergedStaticMetadata=" + mergedStaticMetadata +
", mergedStaticTransitiveMetadata=" + mergedStaticTransitiveMetadata +
", zone='" + zone + '\'' +
", region='" + region + '\'' +
", campus='" + campus + '\'' +
'}';
}
}

@ -18,6 +18,7 @@
package com.tencent.cloud.common.metadata.config;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -42,6 +43,11 @@ public class MetadataAutoConfiguration {
return new MetadataLocalProperties();
}
@Bean
public StaticMetadataManager metadataManager(MetadataLocalProperties metadataLocalProperties) {
return new StaticMetadataManager(metadataLocalProperties);
}
/**
* Create when gateway application is SCG.
*/

@ -20,8 +20,11 @@ package com.tencent.cloud.common.util;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* the utils of parse address.
*
@ -36,6 +39,9 @@ public final class AddressUtils {
}
public static List<String> parseAddressList(String addressInfo) {
if (!StringUtils.hasText(addressInfo)) {
return Collections.emptyList();
}
List<String> addressList = new ArrayList<>();
String[] addresses = addressInfo.split(ADDRESS_SEPARATOR);
for (String address : addresses) {

@ -40,6 +40,7 @@ public class ApplicationContextAwareUtils implements ApplicationContextAware {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextAwareUtils.applicationContext = applicationContext;
}

@ -0,0 +1,51 @@
/*
* 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* the utils for bean factory.
* @author lepdou 2022-05-23
*/
public class BeanFactoryUtils {
public static <T> List<T> getBeans(BeanFactory beanFactory, Class<T> requiredType) {
if (!(beanFactory instanceof DefaultListableBeanFactory)) {
throw new RuntimeException("bean factory not support get list bean. factory type = " + beanFactory.getClass()
.getName());
}
String[] beanNames = ((DefaultListableBeanFactory) beanFactory).getBeanNamesForType(requiredType);
if (beanNames.length == 0) {
return Collections.emptyList();
}
return Arrays.stream(beanNames).map(
beanName -> beanFactory.getBean(beanName, requiredType)
).collect(Collectors.toList());
}
}

@ -0,0 +1,332 @@
/*
* 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 (!isExpressionLabel(labelKey)) {
continue;
}
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 (!isExpressionLabel(labelKey)) {
continue;
}
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 (!isExpressionLabel(labelKey)) {
continue;
}
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;
}
}

@ -69,12 +69,19 @@ public final class JacksonUtils {
public static Map<String, String> deserialize2Map(String jsonStr) {
try {
if (StringUtils.hasText(jsonStr)) {
return OM.readValue(jsonStr, Map.class);
Map<String, Object> temp = OM.readValue(jsonStr, Map.class);
Map<String, String> result = new HashMap<>();
temp.forEach((key, value) -> {
result.put(String.valueOf(key), String.valueOf(value));
});
return result;
}
return new HashMap<>();
}
catch (JsonProcessingException e) {
LOG.error("Json to map failed. check if the format of the json string[{}] is correct.", jsonStr, e);
LOG.error(
"Json to map failed. check if the format of the json string[{}] is correct.",
jsonStr, e);
throw new RuntimeException("Json to map failed.", e);
}
}

@ -32,6 +32,9 @@ public final class ReflectionUtils {
public static Object getFieldValue(Object instance, String fieldName) {
Field field = org.springframework.util.ReflectionUtils.findField(instance.getClass(), fieldName);
if (field == null) {
return null;
}
field.setAccessible(true);
try {

@ -29,7 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Test for {@link MetadataContextHolder}
* Test for {@link MetadataContextHolder}.
*
* @author Haotian Zhang
*/
@ -45,10 +45,10 @@ public class MetadataContextHolderTest {
customMetadata.put("a", "1");
customMetadata.put("b", "2");
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.putAllTransitiveCustomMetadata(customMetadata);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, customMetadata);
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("b")).isEqualTo("2");
@ -60,7 +60,7 @@ public class MetadataContextHolderTest {
customMetadata.put("c", "3");
MetadataContextHolder.init(customMetadata);
metadataContext = MetadataContextHolder.get();
customMetadata = metadataContext.getAllTransitiveCustomMetadata();
customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
Assertions.assertThat(customMetadata.get("a")).isEqualTo("1");
Assertions.assertThat(customMetadata.get("b")).isEqualTo("22");
Assertions.assertThat(customMetadata.get("c")).isEqualTo("3");

@ -27,7 +27,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Test for {@link MetadataLocalProperties}
* Test for {@link MetadataLocalProperties}.
*
* @author Haotian Zhang
*/
@ -42,14 +42,17 @@ public class MetadataLocalPropertiesTest {
@Test
public void test1() {
Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("a"))
.isEqualTo("1");
Assertions.assertThat(metadataLocalProperties.getContent().get("b"))
.isEqualTo("2");
Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull();
}
@Test
public void test2() {
Assertions.assertThat(metadataLocalProperties.getTransitive().contains("b")).isTrue();
Assertions.assertThat(metadataLocalProperties.getTransitive().contains("b"))
.isTrue();
}
@SpringBootApplication

@ -0,0 +1,66 @@
/*
* 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.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
/**
* test for {@link AddressUtils}
*@author lepdou 2022-05-27
*/
@RunWith(MockitoJUnitRunner.class)
public class AddressUtilsTest {
@Test
public void testEmptyStr() {
List<String> result = AddressUtils.parseAddressList("");
Assert.assertEquals(0, result.size());
}
@Test
public void testNullStr() {
List<String> result = AddressUtils.parseAddressList(null);
Assert.assertEquals(0, result.size());
}
@Test
public void testOneStr() {
String host1 = "http://localhost";
List<String> result = AddressUtils.parseAddressList(host1);
Assert.assertEquals(1, result.size());
Assert.assertTrue(result.contains("localhost"));
}
@Test
public void testMultiStr() {
String host1 = "http://localhost";
String host2 = "http://localhost2";
String host3 = "http://localhost3";
List<String> result = AddressUtils.parseAddressList(host1 + "," + host2 + "," + host3);
Assert.assertEquals(3, result.size());
Assert.assertTrue(result.contains("localhost"));
Assert.assertTrue(result.contains("localhost2"));
Assert.assertTrue(result.contains("localhost3"));
}
}

@ -0,0 +1,246 @@
/*
* 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.net.URI;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.MockCookie;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
/**
* test for {@link ExpressionLabelUtils}
*@author lepdou 2022-05-27
*/
@RunWith(MockitoJUnitRunner.class)
public class ExpressionLabelUtilsTest {
@Test
public void testExpressionLabel() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Assert.assertTrue(ExpressionLabelUtils.isExpressionLabel(validLabel1));
Assert.assertTrue(ExpressionLabelUtils.isExpressionLabel(validLabel2));
Assert.assertTrue(ExpressionLabelUtils.isExpressionLabel(validLabel3));
Assert.assertTrue(ExpressionLabelUtils.isExpressionLabel(validLabel4));
Assert.assertTrue(ExpressionLabelUtils.isExpressionLabel(validLabel5));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel1));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel2));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel3));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel4));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel5));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel6));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel7));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel8));
Assert.assertFalse(ExpressionLabelUtils.isExpressionLabel(invalidLabel9));
}
@Test
public void testEscape() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Assert.assertEquals(validLabel1, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel1)));
Assert.assertEquals(validLabel2, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel2)));
Assert.assertEquals(validLabel3, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel3)));
Assert.assertEquals(validLabel4, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel4)));
Assert.assertEquals(validLabel5, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(validLabel5)));
Assert.assertEquals(invalidLabel1, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel1)));
Assert.assertEquals(invalidLabel2, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel2)));
Assert.assertEquals(invalidLabel3, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel3)));
Assert.assertEquals(invalidLabel4, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel4)));
Assert.assertEquals(invalidLabel5, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel5)));
Assert.assertEquals(invalidLabel6, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel6)));
Assert.assertEquals(invalidLabel7, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel7)));
Assert.assertEquals(invalidLabel8, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel8)));
Assert.assertEquals(invalidLabel9, ExpressionLabelUtils.unescape(ExpressionLabelUtils.escape(invalidLabel9)));
}
@Test
public void testResolveHttpServletRequest() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Set<String> labelKeys = Sets.newHashSet(validLabel1, validLabel2, validLabel3, validLabel4, validLabel5,
invalidLabel1, invalidLabel2, invalidLabel3, invalidLabel4, invalidLabel5, invalidLabel6, invalidLabel7,
invalidLabel8, invalidLabel9);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setQueryString("uid=zhangsan");
request.addHeader("uid", "zhangsan");
request.setCookies(new MockCookie("uid", "zhangsan"));
request.setMethod(HttpMethod.GET.name());
request.setRequestURI("/users");
Map<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));
Assert.assertEquals("zhangsan", result.get(validLabel3));
Assert.assertEquals("GET", result.get(validLabel4));
Assert.assertEquals("/users", result.get(validLabel5));
Assert.assertNull(result.get(invalidLabel1));
Assert.assertNull(result.get(invalidLabel2));
Assert.assertNull(result.get(invalidLabel3));
Assert.assertNull(result.get(invalidLabel4));
Assert.assertNull(result.get(invalidLabel5));
Assert.assertNull(result.get(invalidLabel6));
Assert.assertNull(result.get(invalidLabel7));
Assert.assertNull(result.get(invalidLabel8));
Assert.assertNull(result.get(invalidLabel9));
}
@Test
public void testResolveServerWebExchange() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Set<String> labelKeys = Sets.newHashSet(validLabel1, validLabel2, validLabel3, validLabel4, validLabel5,
invalidLabel1, invalidLabel2, invalidLabel3, invalidLabel4, invalidLabel5, invalidLabel6, invalidLabel7,
invalidLabel8, invalidLabel9);
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("http://calleeService/user/get?uid=zhangsan")
.header("uid", "zhangsan")
.cookie(new HttpCookie("uid", "zhangsan")).build();
MockServerWebExchange exchange = new MockServerWebExchange.Builder(httpRequest).build();
Map<String, String> result = ExpressionLabelUtils.resolve(exchange, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));
Assert.assertEquals("zhangsan", result.get(validLabel3));
Assert.assertEquals("GET", result.get(validLabel4));
Assert.assertEquals("/user/get", result.get(validLabel5));
Assert.assertNull(result.get(invalidLabel1));
Assert.assertNull(result.get(invalidLabel2));
Assert.assertNull(result.get(invalidLabel3));
Assert.assertNull(result.get(invalidLabel4));
Assert.assertNull(result.get(invalidLabel5));
Assert.assertNull(result.get(invalidLabel6));
Assert.assertNull(result.get(invalidLabel7));
Assert.assertNull(result.get(invalidLabel8));
Assert.assertNull(result.get(invalidLabel9));
}
@Test
public void testResolveHttpRequest() {
String validLabel1 = "${http.query.uid}";
String validLabel2 = "${http.header.uid}";
String validLabel3 = "${http.cookie.uid}";
String validLabel4 = "${http.method}";
String validLabel5 = "${http.uri}";
String invalidLabel1 = "${http.queryuid}";
String invalidLabel2 = "{http.query.uid}";
String invalidLabel3 = "${http.query.uid";
String invalidLabel4 = "$ {http.query.uid}";
String invalidLabel5 = "${ http.query.uid}";
String invalidLabel6 = "${query.uid}";
String invalidLabel7 = "http.query.uid";
String invalidLabel8 = "$${http.uri}";
String invalidLabel9 = "#{http.uri}";
Set<String> labelKeys = Sets.newHashSet(validLabel1, validLabel2, validLabel3, validLabel4, validLabel5,
invalidLabel1, invalidLabel2, invalidLabel3, invalidLabel4, invalidLabel5, invalidLabel6, invalidLabel7,
invalidLabel8, invalidLabel9);
MockClientHttpRequest request = new MockClientHttpRequest();
request.setMethod(HttpMethod.GET);
request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan"));
request.getHeaders().add("uid", "zhangsan");
Map<String, String> result = ExpressionLabelUtils.resolve(request, labelKeys);
Assert.assertEquals("zhangsan", result.get(validLabel1));
Assert.assertEquals("zhangsan", result.get(validLabel2));
Assert.assertNull(result.get(validLabel3));
Assert.assertEquals("GET", result.get(validLabel4));
Assert.assertEquals("/user/get", result.get(validLabel5));
Assert.assertNull(result.get(invalidLabel1));
Assert.assertNull(result.get(invalidLabel2));
Assert.assertNull(result.get(invalidLabel3));
Assert.assertNull(result.get(invalidLabel4));
Assert.assertNull(result.get(invalidLabel5));
Assert.assertNull(result.get(invalidLabel6));
Assert.assertNull(result.get(invalidLabel7));
Assert.assertNull(result.get(invalidLabel8));
Assert.assertNull(result.get(invalidLabel9));
}
}

@ -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.common.util;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Test for {@link JacksonUtils}.
*
* @author lepdou, Haotian Zhang
*/
@RunWith(MockitoJUnitRunner.class)
public class JacksonUtilsTest {
@Test
public void testSerialize2Json() {
Map<String, String> sourceMap = new HashMap<>();
sourceMap.put("k1", "v1");
sourceMap.put("k2", "v2");
sourceMap.put("k3", "v3");
String jsonStr = JacksonUtils.serialize2Json(sourceMap);
assertThat(jsonStr).isEqualTo("{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"}");
}
@Test
public void testDeserialize2Map() {
String jsonStr = "{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"}";
Map<String, String> map = JacksonUtils.deserialize2Map(jsonStr);
assertThat(map.size()).isEqualTo(3);
assertThat(map.get("k1")).isEqualTo("v1");
assertThat(map.get("k2")).isEqualTo("v2");
assertThat(map.get("k3")).isEqualTo("v3");
assertThat(JacksonUtils.deserialize2Map("")).isNotNull();
assertThat(JacksonUtils.deserialize2Map("")).isEmpty();
jsonStr = "{\"k1\":\"v1\",\"k2\":\"v2\",\"k3\":\"v3\"";
try {
JacksonUtils.deserialize2Map(jsonStr);
fail("RuntimeException should be thrown.");
}
catch (RuntimeException exception) {
assertThat(exception.getMessage()).isEqualTo("Json to map failed.");
}
catch (Throwable throwable) {
fail("RuntimeException should be thrown.");
}
}
}

@ -0,0 +1,46 @@
/*
* 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.io.IOException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
/**
* test for {@link ResourceFileUtils}
*@author lepdou 2022-05-27
*/
@RunWith(MockitoJUnitRunner.class)
public class ResourceFileUtilsTest {
@Test
public void testReadExistedFile() throws IOException {
String content = ResourceFileUtils.readFile("test.txt");
Assert.assertEquals("just for test\n", content);
}
@Test
public void testReadNotExistedFile() throws IOException {
String content = ResourceFileUtils.readFile("not_existed_test.txt");
Assert.assertEquals("", content);
}
}

@ -70,9 +70,11 @@
</developers>
<properties>
<revision>1.5.0-2020.0.5-SNAPSHOT</revision>
<polaris.version>1.6.0</polaris.version>
<powermock.version>2.0.0</powermock.version>
<revision>1.5.2-2020.0.5-SNAPSHOT</revision>
<polaris.version>1.6.1</polaris.version>
<logback.version>1.2.7</logback.version>
<mocktio.version>4.5.1</mocktio.version>
<byte-buddy.version>1.12.10</byte-buddy.version>
<!-- Maven Plugin Versions -->
<maven-source-plugin.version>3.2.0</maven-source-plugin.version>
@ -145,18 +147,31 @@
<version>${revision}</version>
</dependency>
<!-- powermock-module-junit4 -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- powermock-api-mockito -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mocktio.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mocktio.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${byte-buddy.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

@ -53,7 +53,7 @@ public class MetadataCalleeController {
// Get Custom Metadata From Context
MetadataContext context = MetadataContextHolder.get();
Map<String, String> customMetadataMap = context.getAllTransitiveCustomMetadata();
Map<String, String> customMetadataMap = context.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
customMetadataMap.forEach((key, value) -> {
LOG.info("Custom Metadata (Key-Value): {} : {}", key, value);

@ -5,7 +5,7 @@ spring:
name: MetadataCalleeService
cloud:
polaris:
address: grpc://127.0.0.1:8091
address: grpc://183.47.111.80:8091
namespace: default
enabled: true
discovery:

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save