diff --git a/README.md b/README.md
index dc3507f64a..d635a97be9 100755
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Sentinel: Sentinel of Your Application
+# Sentinel: The Sentinel of Your Microservices
[![Travis Build Status](https://travis-ci.org/alibaba/Sentinel.svg?branch=master)](https://travis-ci.org/alibaba/Sentinel)
[![Codecov](https://codecov.io/gh/alibaba/Sentinel/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/Sentinel)
@@ -30,8 +30,9 @@ See the [中文文档](https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%
See the [Wiki](https://github.com/alibaba/Sentinel/wiki) for full documentation, examples, blog posts, operational details and other information.
-Sentinel provides integration module for various open-source frameworks and libraries
-(e.g. Spring Cloud, Apache Dubbo, gRPC, Spring WebFlux, Reactor). You can refer to [the document](https://github.com/alibaba/Sentinel/wiki/Adapters-to-Popular-Framework) for more information.
+Sentinel provides integration modules for various open-source frameworks
+(e.g. Spring Cloud, Apache Dubbo, gRPC, Spring WebFlux, Reactor) and service mesh.
+You can refer to [the document](https://github.com/alibaba/Sentinel/wiki/Adapters-to-Popular-Framework) for more information.
If you are using Sentinel, please [**leave a comment here**](https://github.com/alibaba/Sentinel/issues/18) to tell us your scenario to make Sentinel better.
It's also encouraged to add the link of your blog post, tutorial, demo or customized components to [**Awesome Sentinel**](./doc/awesome-sentinel.md).
@@ -55,7 +56,7 @@ If your application is build in Maven, just add the following dependency in `pom
com.alibaba.cspsentinel-core
- 1.6.1
+ 1.7.1
```
@@ -74,6 +75,7 @@ try (Entry entry = SphU.entry("HelloWorld")) {
// Handle rejected request.
e.printStackTrace();
}
+// try-with-resources auto exit
```
So far the code modification is done. We also provide [annotation support module](https://github.com/alibaba/Sentinel/blob/master/sentinel-extension/sentinel-annotation-aspectj/README.md) to define resource easier.
@@ -98,18 +100,18 @@ For more information, please refer to [How To Use](https://github.com/alibaba/Se
### 4. Check the Result
-After running the demo for a while, you can see the following records in `~/logs/csp/${appName}-metrics.log`.
+After running the demo for a while, you can see the following records in `~/logs/csp/${appName}-metrics.log.{date}` (When using the default `DateFileLogHandler`).
```
-|--timestamp-|------date time----|-resource-|p |block|s |e|rt
-1529998904000|2018-06-26 15:41:44|HelloWorld|20|0 |20|0|0
-1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728
-1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0
-1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0
-1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0
-1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0
-
-p stands for incoming request, block for blocked by rules, success for success handled by Sentinel, e for exception count, rt for average response time (ms)
+|--timestamp-|------date time----|-resource-|p |block|s |e|rt |occupied
+1529998904000|2018-06-26 15:41:44|HelloWorld|20|0 |20|0|0 |0
+1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728 |0
+1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0 |0
+1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0 |0
+1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0 |0
+1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0 |0
+
+p stands for incoming request, block for blocked by rules, success for success handled by Sentinel, e for exception count, rt for average response time (ms), occupied stands for occupiedPassQps since 1.5.0 which enable us booking more than 1 shot when entering.
```
This shows that the demo can print "hello world" 20 times per second.
@@ -124,11 +126,14 @@ Samples can be found in the [sentinel-demo](https://github.com/alibaba/Sentinel/
Sentinel also provides a simple dashboard application, on which you can monitor the clients and configure the rules in real time.
+![dashboard](https://user-images.githubusercontent.com/9434884/55449295-84866d80-55fd-11e9-94e5-d3441f4a2b63.png)
+
For details please refer to [Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard).
## Trouble Shooting and Logs
-Sentinel will generate logs for troubleshooting. All the information can be found in [logs](https://github.com/alibaba/Sentinel/wiki/Logs).
+Sentinel will generate logs for troubleshooting and real-time monitoring.
+All the information can be found in [logs](https://github.com/alibaba/Sentinel/wiki/Logs).
## Bugs and Feedback
@@ -163,6 +168,5 @@ These are only part of the companies using Sentinel, for reference only. If you
![亲宝宝](https://stlib.qbb6.com/wclt/img/home_hd/version1/title_logo.png)
![杭州光云科技](https://www.raycloud.com/images/logo.png)
![金汇金融](https://res.jinhui365.com/r/images/logo2.png?v=1.527)
-![Vivo](https://user-images.githubusercontent.com/9434884/49355264-c6f87600-f701-11e8-8109-054cf91df868.png)
![闪电购](http://cdn.52shangou.com/shandianbang/official-source/3.1.1/build/images/logo.png)
![拼多多](http://cdn.pinduoduo.com/assets/img/pdd_logo_v3.png)
diff --git a/doc/awesome-sentinel.md b/doc/awesome-sentinel.md
index db2715777d..e560bf5de5 100644
--- a/doc/awesome-sentinel.md
+++ b/doc/awesome-sentinel.md
@@ -2,52 +2,74 @@
[![Awesome](https://awesome.re/badge-flat.svg)](https://awesome.re)
-A curated list of awesome things (e.g. sample, extensions, blogs) for [Sentinel](https://github.com/alibaba/Sentinel).
+A curated list of awesome things (e.g. samples, third-party extensions, blog posts) for [Sentinel](https://github.com/alibaba/Sentinel).
-If you want your component to appear here, send a pull request to this repository to add it.
+If you want your component to appear here, feel free to submit a pull request to this repository to add it.
You can refer to the [awesome contribution guidelines](https://github.com/sentinel-group/sentinel-awesome/blob/master/CONTRIBUTING.md).
You can also add to [sentinel-group/sentinel-awesome](https://github.com/sentinel-group/sentinel-awesome).
## Contents
+- [Presentations](#presentations)
- [Tutorials](#tutorials)
-- [Samples / Demos](#samples--demos)
+- [Demos](#demos)
- [Extensions / Integrations](#extensions--integrations)
-- [Blogs](#blogs)
+- [Blog Posts](#blog-posts)
+
+## Presentations
+
+- Sentinel 1.6.0 网关流控新特性介绍-Eric Zhao (Dubbo Tech Day-201905-Beijing): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%201.6.0%20网关流控新特性介绍-Eric%20Zhao-DTED-201905.pdf)
+- Sentinel 微服务流控降级实践-Eric Zhao (Dubbo Tech Day-201907-Shenzhen): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%20微服务流控降级实践-Eric%20Zhao-DTED-201907.pdf)
+- Sentinel 1.7.0 新特性展望-Eric Zhao (Dubbo Tech Day-201910-Chengdu): [PDF](https://github.com/sentinel-group/sentinel-awesome/blob/master/slides/Sentinel%201.7.0%20新特性展望-Eric%20Zhao-DTED-201910.pdf)
## Tutorials
-## Samples / Demos
+- [Sentinel Guides](https://github.com/sentinel-group/sentinel-guides)
+
+## Demos
- [sentinel-zuul-example](https://github.com/tigerMoon/sentinel-zuul-sample): A simple project integration Sentinel to Spring Cloud Zuul which provide Service and API Path level flow control management by [tiger](https://github.com/tigerMoon)
## Extensions / Integrations
- [sentinel-support](https://github.com/cdfive/sentinel-support): A support project for convenient Sentinel integration including properties file configuration, ActiveMQ integration and a JdbcDataSource implementation by [cdfive](https://github.com/cdfive)
-- [sentinel-multiDataSource-adapter](https://github.com/finefuture/sentinel-dashboard-X): Sentinel-dashborad multi-data source adapter has integrated Apollo configuration center and Nacos configuration center for bidirectional modification persistence, implementation by [finefuture](https://github.com/finefuture)
+- [Sentinel dashboard multi-data-source adapter](https://github.com/finefuture/sentinel-dashboard-X): Sentinel dashboard multi-data-source adapter has integrated Apollo and Nacos configuration center for bidirectional modification persistence. Implemented by [finefuture](https://github.com/finefuture)
+- [Sentinel Rule Annotation Support](https://github.com/code1986/sentinel-lib): A third-party library that supports configuring flow rule and degrade rule using annotation. Implemented by [code1986](https://github.com/code1986)
+- [sentinel-pigeon-adapter](https://github.com/wchswchs/sentinel-pigeon): A RPC framework Pigeon adapter for Sentinel including provider and invoker rate limiting implementation by [wchswchs](https://github.com/wchswchs)
-## Blogs
+## Blog Posts
- [Sentinel 为 Dubbo 服务保驾护航](http://dubbo.apache.org/zh-cn/blog/sentinel-introduction-for-dubbo.html) by [Eric Zhao](https://github.com/sczyh30)
-- [Sentinel 与 Hystrix 的对比](https://github.com/alibaba/Sentinel/wiki/Sentinel-%E4%B8%8E-Hystrix-%E7%9A%84%E5%AF%B9%E6%AF%94) by [Eric Zhao](https://github.com/sczyh30)
-- [Guideline: 从 Hystrix 迁移到 Sentinel](https://github.com/alibaba/Sentinel/wiki/Guideline:-%E4%BB%8E-Hystrix-%E8%BF%81%E7%A7%BB%E5%88%B0-Sentinel) by [Eric Zhao](https://github.com/sczyh30)
-- [Sentinel 控制台监控数据持久化【MySQL】(Spring Data JPA)](https://www.cnblogs.com/cdfive2018/p/9838577.html) by [cdfive](https://github.com/cdfive)
+- [在生产环境中使用 Sentinel 控制台](https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel) by [Eric Zhao](https://github.com/sczyh30)
+- [Sentinel 与 Hystrix 的对比](https://sentinelguard.io/zh-cn/blog/sentinel-vs-hystrix.html) by [Eric Zhao](https://github.com/sczyh30)
+- [Guideline: 从 Hystrix 迁移到 Sentinel](https://sentinelguard.io/zh-cn/blog/guideline-migrate-from-hystrix-to-sentinel.html) by [Eric Zhao](https://github.com/sczyh30)
+- [Sentinel 控制台监控数据持久化【MySQL】](https://www.cnblogs.com/cdfive2018/p/9838577.html) by [cdfive](https://github.com/cdfive)
- [Sentinel 控制台监控数据持久化【InfluxDB】](https://www.cnblogs.com/cdfive2018/p/9914838.html) by [cdfive](https://github.com/cdfive)
-- [Sentinel一体化监控解决方案 CrateDB+Grafana](https://blog.csdn.net/huyong1990/article/details/82392386) by [Young Hu](https://github.com/YoungHu)
-- [Sentinel 原理-全解析](https://mp.weixin.qq.com/s/7_pCkamNv0269e5l9_Wz7w) by [houyi](https://github.com/all4you)
-- [Sentinel 原理-调用链](https://mp.weixin.qq.com/s/UEzwD22YC6jpp02foNSXnw) by [houyi](https://github.com/all4you)
-- [Sentinel 原理-滑动窗口](https://mp.weixin.qq.com/s/B1_7Kb_CxeKEAv43kdCWOA) by [houyi](https://github.com/all4you)
-- [Sentinel 实战-限流](https://mp.weixin.qq.com/s/rjyU37Dm-sxNln7GUD8tOw) by [houyi](https://github.com/all4you)
-- [Sentinel 实战-控制台](https://mp.weixin.qq.com/s/23EDFHMXLwsDqw-4O5dR5A) by [houyi](https://github.com/all4you)
-- [Sentinel 实战-规则持久化](https://mp.weixin.qq.com/s/twMFiBfRawKLR-1-N-f1yw) by [houyi](https://github.com/all4you)
-- [sentinel 深入浅出之原理篇SlotChain](https://www.jianshu.com/p/a7a405de3a12) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇Context初始化&Entry初始化](https://www.jianshu.com/p/e39ac47cd893) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇NodeSelectorSlot](https://www.jianshu.com/p/9a380ba188ab) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇ClusterBuilderSlot](https://www.jianshu.com/p/0b0b5d8888a2) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇StatisticSlot&滑动窗口](https://www.jianshu.com/p/9620298fd15a) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇SystemSlot](https://www.jianshu.com/p/bfad1b7d0cde) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇AuthoritySlot](https://www.jianshu.com/p/c5312c2242b3) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇FlowSlot](https://www.jianshu.com/p/53218d0d273e) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇DegradeSlot](https://www.jianshu.com/p/e910d4840e4a) by [shxz130](https://github.com/shxz130)
-- [sentinel 深入浅出之原理篇协议拓展dubbo,grpc,web-servlet](https://www.jianshu.com/p/579bff0f34be) by [shxz130](https://github.com/shxz130)
+- [Sentinel 控制台监控数据持久化【Apollo】](https://blog.csdn.net/caodegao/article/details/100009618) by [cookiejoo](https://github.com/cookiejoo)
+- [Sentinel一体化监控解决方案 CrateDB + Grafana](https://blog.csdn.net/huyong1990/article/details/82392386) by [Young Hu](https://github.com/YoungHu)
+- Sentinel 源码解析系列 by [houyi](https://github.com/all4you)
+ - [Sentinel 原理-全解析](https://mp.weixin.qq.com/s/7_pCkamNv0269e5l9_Wz7w)
+ - [Sentinel 原理-调用链](https://mp.weixin.qq.com/s/UEzwD22YC6jpp02foNSXnw)
+ - [Sentinel 原理-滑动窗口](https://mp.weixin.qq.com/s/B1_7Kb_CxeKEAv43kdCWOA)
+ - [Sentinel 实战-限流](https://mp.weixin.qq.com/s/rjyU37Dm-sxNln7GUD8tOw)
+ - [Sentinel 实战-控制台](https://mp.weixin.qq.com/s/23EDFHMXLwsDqw-4O5dR5A)
+ - [Sentinel 实战-规则持久化](https://mp.weixin.qq.com/s/twMFiBfRawKLR-1-N-f1yw)
+- Sentinel 学习笔记 by [ro9er](https://github.com/ro9er)
+ - [Sentinel 学习笔记(1)-- 流量统计代码解析](https://www.jianshu.com/p/7936d7a57924)
+ - [Sentinel 学习笔记(2)-- 流量控制代码分析](https://www.jianshu.com/p/938709e94e43)
+ - [Sentinel 学习笔记(3)-- 上下文统计Node建立分析](https://www.jianshu.com/p/cfdf525248c1)
+- [大流量下的服务质量治理 Dubbo Sentinel 初涉](https://mp.weixin.qq.com/s/ergr_siI07VwwSRPFgsLvQ) by [RyuGrade](https://github.com/RyuGrade)
+- Sentinel 深入浅出系列 by [shxz130](https://github.com/shxz130)
+ - [Sentinel 深入浅出之原理篇 SlotChain](https://www.jianshu.com/p/a7a405de3a12)
+ - [Sentinel 深入浅出之原理篇 Context初始化 & Entry初始化](https://www.jianshu.com/p/e39ac47cd893)
+ - [Sentinel 深入浅出之原理篇 NodeSelectorSlot](https://www.jianshu.com/p/9a380ba188ab)
+ - [Sentinel 深入浅出之原理篇 ClusterBuilderSlot](https://www.jianshu.com/p/0b0b5d8888a2)
+ - [Sentinel 深入浅出之原理篇 StatisticSlot&滑动窗口](https://www.jianshu.com/p/9620298fd15a)
+ - [Sentinel 深入浅出之原理篇 SystemSlot](https://www.jianshu.com/p/bfad1b7d0cde)
+ - [Sentinel 深入浅出之原理篇 AuthoritySlot](https://www.jianshu.com/p/c5312c2242b3)
+ - [Sentinel 深入浅出之原理篇 FlowSlot](https://www.jianshu.com/p/53218d0d273e)
+ - [Sentinel 深入浅出之原理篇 DegradeSlot](https://www.jianshu.com/p/e910d4840e4a)
+- [Alibaba Sentinel RESTful 接口流控处理优化](https://www.jianshu.com/p/96f5980d9798) by [luanlouis](https://github.com/luanlouis)
+- [Sentinel 控制台前端开发环境搭建](https://www.cnblogs.com/cdfive2018/p/11084001.html) by [cdfive](https://github.com/cdfive)
+- [阿里 Sentinel 源码解析](https://www.javadoop.com/post/sentinel) by [Javadoop](https://www.javadoop.com)
diff --git a/pom.xml b/pom.xml
index 944bfc7a16..2d80de934b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.alibaba.cspsentinel-parent
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOTpom${project.artifactId}
@@ -41,7 +41,7 @@
- 1.2.56
+ 1.2.624.12
@@ -120,11 +120,21 @@
sentinel-datasource-apollo${project.version}
+
+ com.alibaba.csp
+ sentinel-datasource-etcd
+ ${project.version}
+ com.alibaba.cspsentinel-transport-simple-http${project.version}
+
+ com.alibaba.csp
+ sentinel-transport-netty-http
+ ${project.version}
+ com.alibaba.cspsentinel-transport-common
@@ -288,6 +298,11 @@
maven-jar-plugin${maven.jar.version}
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven.gpg.version}
+
diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml
index 038652a4cf..1f152c169a 100755
--- a/sentinel-adapter/pom.xml
+++ b/sentinel-adapter/pom.xml
@@ -7,7 +7,7 @@
com.alibaba.cspsentinel-parent
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOTsentinel-adapterpom
@@ -24,6 +24,7 @@
sentinel-spring-webflux-adaptersentinel-api-gateway-adapter-commonsentinel-spring-cloud-gateway-adapter
+ sentinel-spring-webmvc-adapter
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml
index d6a4bab392..5ccf18467c 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml
@@ -5,7 +5,7 @@
sentinel-adaptercom.alibaba.csp
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOT4.0.0
@@ -15,7 +15,7 @@
1.81.8
- 2.7.1
+ 2.7.3
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java
new file mode 100644
index 0000000000..9ea01e59da
--- /dev/null
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.adapter.dubbo;
+
+
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.ListenableFilter;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcContext;
+
+/**
+ * Base Class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}.
+ *
+ * @author Zechao Zheng
+ */
+
+public abstract class BaseSentinelDubboFilter extends ListenableFilter {
+ public BaseSentinelDubboFilter() {
+ this.listener = new SentinelDubboListener();
+ }
+
+ static class SentinelDubboListener implements Listener {
+
+ @Override
+ public void onResponse(Result appResponse, Invoker> invoker, Invocation invocation) {
+ if (DubboConfig.getDubboBizExceptionTraceEnabled()) {
+ traceAndExit(appResponse.getException(), invoker.getUrl());
+ } else {
+ traceAndExit(null, invoker.getUrl());
+ }
+ }
+
+ @Override
+ public void onError(Throwable t, Invoker> invoker, Invocation invocation) {
+ traceAndExit(t, invoker.getUrl());
+ }
+
+ }
+
+ static void traceAndExit(Throwable throwable, URL url) {
+ Entry interfaceEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY);
+ Entry methodEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY);
+ if (methodEntry != null) {
+ Tracer.traceEntry(throwable, methodEntry);
+ methodEntry.exit();
+ RpcContext.getContext().remove(DubboUtils.DUBBO_METHOD_ENTRY_KEY);
+ }
+ if (interfaceEntry != null) {
+ Tracer.traceEntry(throwable, interfaceEntry);
+ interfaceEntry.exit();
+ RpcContext.getContext().remove(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY);
+ }
+ if (CommonConstants.PROVIDER_SIDE.equals(url.getParameter(CommonConstants.SIDE_KEY))) {
+ ContextUtil.exit();
+ }
+ }
+}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java
index 97ae51bac3..b73542931b 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java
@@ -15,7 +15,7 @@
*/
package com.alibaba.csp.sentinel.adapter.dubbo;
-import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
@@ -24,17 +24,19 @@
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
+import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
+
/**
* Puts current consumer's application name in the attachment of each invocation.
*
* @author Eric Zhao
*/
-@Activate(group = "consumer")
+@Activate(group = CONSUMER)
public class DubboAppContextFilter implements Filter {
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
- String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
+ String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY);
if (application != null) {
RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application);
}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java
index 03a494f585..d2c325fafd 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java
@@ -15,6 +15,8 @@
*/
package com.alibaba.csp.sentinel.adapter.dubbo;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.util.StringUtil;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
@@ -24,6 +26,8 @@
public final class DubboUtils {
public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication";
+ public static final String DUBBO_METHOD_ENTRY_KEY = "dubboMethodEntry";
+ public static final String DUBBO_INTERFACE_ENTRY_KEY = "dubboInterfaceEntry";
public static String getApplication(Invocation invocation, String defaultValue) {
if (invocation == null || invocation.getAttachments() == null) {
@@ -32,9 +36,14 @@ public static String getApplication(Invocation invocation, String defaultValue)
return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue);
}
- public static String getResourceName(Invoker> invoker, Invocation invocation) {
+ public static String getResourceName(Invoker> invoker, Invocation invocation){
+ return getResourceName(invoker, invocation, false);
+ }
+
+ public static String getResourceName(Invoker> invoker, Invocation invocation, Boolean useGroupAndVersion) {
StringBuilder buf = new StringBuilder(64);
- buf.append(invoker.getInterface().getName())
+ String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName();
+ buf.append(interfaceResource)
.append(":")
.append(invocation.getMethodName())
.append("(");
@@ -50,5 +59,16 @@ public static String getResourceName(Invoker> invoker, Invocation invocation)
return buf.toString();
}
- private DubboUtils() {}
+ public static String getResourceName(Invoker> invoker, Invocation invocation, String prefix) {
+ if (StringUtil.isNotBlank(prefix)) {
+ return new StringBuilder(64)
+ .append(prefix)
+ .append(getResourceName(invoker, invocation, DubboConfig.getDubboInterfaceGroupAndVersionEnabled()))
+ .toString();
+ } else {
+ return getResourceName(invoker, invocation, DubboConfig.getDubboInterfaceGroupAndVersionEnabled());
+ }
+ }
+ private DubboUtils() {
+ }
}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java
old mode 100755
new mode 100644
index 2dcbaa79cb..ffa9fba9cf
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java
@@ -17,22 +17,26 @@
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
import com.alibaba.csp.sentinel.SphU;
-import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.BlockException;
-
-import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.InvokeMode;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.support.RpcUtils;
+
+import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
/**
*
Dubbo service consumer filter for Sentinel. Auto activated by default.
- *
+ *
* If you want to disable the consumer filter, you can configure:
*
Apache Dubbo service provider filter that enables integration with Sentinel. Auto activated by default.
*
Note: this only works for Apache Dubbo 2.7.x or above version.
- *
+ *
* If you want to disable the provider filter, you can configure:
*
* <dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
@@ -43,8 +45,8 @@
* @author Carpenter Lee
* @author Eric Zhao
*/
-@Activate(group = "provider")
-public class SentinelDubboProviderFilter implements Filter {
+@Activate(group = PROVIDER)
+public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter {
public SentinelDubboProviderFilter() {
RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
@@ -54,40 +56,26 @@ public SentinelDubboProviderFilter() {
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
// Get origin caller.
String application = DubboUtils.getApplication(invocation, "");
-
+ RpcContext rpcContext = RpcContext.getContext();
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
- String resourceName = DubboUtils.getResourceName(invoker, invocation);
- String interfaceName = invoker.getInterface().getName();
+ String methodResourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix());
+ String interfaceResourceName = DubboConfig.getDubboInterfaceGroupAndVersionEnabled() ? invoker.getUrl().getColonSeparatedKey()
+ : invoker.getInterface().getName();
// Only need to create entrance context at provider side, as context will take effect
// at entrance of invocation chain only (for inbound traffic).
- ContextUtil.enter(resourceName, application);
- interfaceEntry = SphU.entry(interfaceName, EntryType.IN);
- methodEntry = SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
-
- Result result = invoker.invoke(invocation);
- if (result.hasException()) {
- Throwable e = result.getException();
- // Record common exception.
- Tracer.traceEntry(e, interfaceEntry);
- Tracer.traceEntry(e, methodEntry);
- }
- return result;
+ ContextUtil.enter(methodResourceName, application);
+ interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
+ rpcContext.set(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY, interfaceEntry);
+ methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments());
+ rpcContext.set(DubboUtils.DUBBO_METHOD_ENTRY_KEY, methodEntry);
+ return invoker.invoke(invocation);
} catch (BlockException e) {
return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e);
- } catch (RpcException e) {
- Tracer.traceEntry(e, interfaceEntry);
- Tracer.traceEntry(e, methodEntry);
- throw e;
- } finally {
- if (methodEntry != null) {
- methodEntry.exit(1, invocation.getArguments());
- }
- if (interfaceEntry != null) {
- interfaceEntry.exit();
- }
- ContextUtil.exit();
}
}
+
+
}
+
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java
new file mode 100644
index 0000000000..9b2b021828
--- /dev/null
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.adapter.dubbo.config;
+
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ *
+ * Responsible for dubbo service provider, consumer attribute configuration
+ *
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public final class DubboConfig {
+
+ public static final String DUBBO_USE_PREFIX = "csp.sentinel.dubbo.resource.use.prefix";
+ private static final String TRUE_STR = "true";
+
+ public static final String DUBBO_PROVIDER_PREFIX = "csp.sentinel.dubbo.resource.provider.prefix";
+ public static final String DUBBO_CONSUMER_PREFIX = "csp.sentinel.dubbo.resource.consumer.prefix";
+
+ private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:";
+ private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:";
+
+ public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled";
+
+ public static final String TRACE_BIZ_EXCEPTION_ENABLED = "csp.sentinel.dubbo.trace.biz.exception.enabled";
+
+
+ public static boolean isUsePrefix() {
+ return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_USE_PREFIX));
+ }
+
+ public static String getDubboProviderPrefix() {
+ if (isUsePrefix()) {
+ String config = SentinelConfig.getConfig(DUBBO_PROVIDER_PREFIX);
+ return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX;
+ }
+ return null;
+ }
+
+ public static String getDubboConsumerPrefix() {
+ if (isUsePrefix()) {
+ String config = SentinelConfig.getConfig(DUBBO_CONSUMER_PREFIX);
+ return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX;
+ }
+ return null;
+ }
+
+ public static Boolean getDubboInterfaceGroupAndVersionEnabled() {
+ return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED));
+ }
+
+ public static Boolean getDubboBizExceptionTraceEnabled() {
+ String traceBizExceptionEnabled = SentinelConfig.getConfig(TRACE_BIZ_EXCEPTION_ENABLED);
+ if (StringUtil.isNotBlank(traceBizExceptionEnabled)) {
+ return TRUE_STR.equalsIgnoreCase(traceBizExceptionEnabled);
+ }
+ return true;
+ }
+
+}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java
index a61f3b4c80..7793a6567e 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java
@@ -15,26 +15,73 @@
*/
package com.alibaba.csp.sentinel;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
-
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
/**
* Base test class, provide common methods for subClass
* The package is same as CtSph, to call CtSph.resetChainMap() method for test
- *
+ *
* Note: Only for test. DO NOT USE IN PRODUCTION!
*
* @author cdfive
*/
public class BaseTest {
+
+ protected Invoker invoker;
+ protected Invocation invocation;
+
+ public void constructInvokerAndInvocation() {
+ invoker = mock(Invoker.class);
+ URL url = URL.valueOf("dubbo://127.0.0.1:2181")
+ .addParameter(CommonConstants.VERSION_KEY, "1.0.0")
+ .addParameter(CommonConstants.GROUP_KEY, "grp1")
+ .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
+ when(invoker.getUrl()).thenReturn(url);
+ when(invoker.getInterface()).thenReturn(DemoService.class);
+
+ invocation = mock(Invocation.class);
+ Method method = DemoService.class.getMethods()[0];
+ when(invocation.getMethodName()).thenReturn(method.getName());
+ when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
+
+ }
+
/**
* Clean up resources for context, clusterNodeMap, processorSlotChainMap
*/
protected static void cleanUpAll() {
- RpcContext.removeContext();
- ClusterBuilderSlot.getClusterNodeMap().clear();
- CtSph.resetChainMap();
+ try {
+ RpcContext.removeContext();
+ ClusterBuilderSlot.getClusterNodeMap().clear();
+ CtSph.resetChainMap();
+ Method method = ContextUtil.class.getDeclaredMethod("resetContextMap");
+ method.setAccessible(true);
+ method.invoke(null, null);
+ ContextUtil.exit();
+ SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true");
+ FlowRuleManager.loadRules(new ArrayList<>());
+ DegradeRuleManager.loadRules(new ArrayList<>());
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
}
}
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java
index 8d3636f180..ece46fde3d 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java
@@ -15,30 +15,55 @@
*/
package com.alibaba.csp.sentinel.adapter.dubbo;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.HashMap;
-import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* @author cdfive
*/
public class DubboUtilsTest {
+ @Before
+ public void setUp() {
+ SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, "");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, "");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
+ }
+
+
+ @After
+ public void tearDown() {
+ SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, "");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, "");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false");
+ }
+
+
@Test
public void testGetApplication() {
Invocation invocation = mock(Invocation.class);
when(invocation.getAttachments()).thenReturn(new HashMap<>());
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
- .thenReturn("consumerA");
+ .thenReturn("consumerA");
String application = DubboUtils.getApplication(invocation, "");
verify(invocation).getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "");
@@ -51,7 +76,7 @@ public void testGetApplicationNoAttachments() {
Invocation invocation = mock(Invocation.class);
when(invocation.getAttachments()).thenReturn(null);
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
- .thenReturn("consumerA");
+ .thenReturn("consumerA");
DubboUtils.getApplication(invocation, "");
@@ -59,17 +84,66 @@ public void testGetApplicationNoAttachments() {
}
@Test
- public void testGetResourceName() {
+ public void testGetResourceName() throws NoSuchMethodException {
Invoker invoker = mock(Invoker.class);
when(invoker.getInterface()).thenReturn(DemoService.class);
Invocation invocation = mock(Invocation.class);
- Method method = DemoService.class.getMethods()[0];
+ Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
when(invocation.getMethodName()).thenReturn(method.getName());
when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
String resourceName = DubboUtils.getResourceName(invoker, invocation);
assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
+
+ }
+
+ @Test
+ public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodException {
+ Invoker invoker = mock(Invoker.class);
+ URL url = URL.valueOf("dubbo://127.0.0.1:2181")
+ .addParameter(CommonConstants.VERSION_KEY, "1.0.0")
+ .addParameter(CommonConstants.GROUP_KEY, "grp1")
+ .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName());
+ when(invoker.getUrl()).thenReturn(url);
+ when(invoker.getInterface()).thenReturn(DemoService.class);
+
+ Invocation invocation = mock(Invocation.class);
+ Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
+ when(invocation.getMethodName()).thenReturn(method.getName());
+ when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
+
+ String resourceNameUseGroupAndVersion = DubboUtils.getResourceName(invoker, invocation, true);
+
+ assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion);
+ }
+
+
+ @Test
+ public void testGetResourceNameWithPrefix() throws NoSuchMethodException {
+ Invoker invoker = mock(Invoker.class);
+ when(invoker.getInterface()).thenReturn(DemoService.class);
+
+ Invocation invocation = mock(Invocation.class);
+ Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class);
+ when(invocation.getMethodName()).thenReturn(method.getName());
+ when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
+
+ //test with default prefix
+ String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix());
+ assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
+ resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix());
+ assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
+
+
+ //test with custom prefix
+ SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, "my:dubbo:provider:");
+ SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, "my:dubbo:consumer:");
+ resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix());
+ assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
+ resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix());
+ assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName);
+
}
-}
+}
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java
index e4b30b95e0..f949231bc3 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java
@@ -16,9 +16,12 @@
package com.alibaba.csp.sentinel.adapter.dubbo;
import com.alibaba.csp.sentinel.BaseTest;
-import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig;
+import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback;
+import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback;
+import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
@@ -27,20 +30,40 @@
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.node.StatisticNode;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
-
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.support.RpcUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Set;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;
+import static org.apache.dubbo.rpc.Constants.ASYNC_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* @author cdfive
@@ -49,28 +72,191 @@ public class SentinelDubboConsumerFilterTest extends BaseTest {
private SentinelDubboConsumerFilter filter = new SentinelDubboConsumerFilter();
+
@Before
public void setUp() {
cleanUpAll();
+ initFallback();
+ constructInvokerAndInvocation();
}
@After
public void cleanUp() {
cleanUpAll();
+ DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback());
+ }
+
+ public void initFlowRule(String resource) {
+ FlowRule flowRule = new FlowRule(resource);
+ flowRule.setCount(1);
+ flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
+ List flowRules = new ArrayList<>();
+ flowRules.add(flowRule);
+ FlowRuleManager.loadRules(flowRules);
+ }
+
+ public void initDegradeRule(String resource) {
+ DegradeRule degradeRule = new DegradeRule(resource)
+ .setCount(0.5)
+ .setGrade(DEGRADE_GRADE_EXCEPTION_RATIO);
+ List degradeRules = new ArrayList<>();
+ degradeRules.add(degradeRule);
+ degradeRule.setTimeWindow(1);
+ DegradeRuleManager.loadRules(degradeRules);
+ }
+
+
+ public void initFallback() {
+ DubboFallbackRegistry.setConsumerFallback(new DubboFallback() {
+ @Override
+ public Result handle(Invoker> invoker, Invocation invocation, BlockException ex) {
+ boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation);
+ Result fallbackResult = null;
+ fallbackResult = AsyncRpcResult.newDefaultAsyncResult("fallback", invocation);
+ return fallbackResult;
+ }
+ });
+ }
+
+ @Test
+ public void testInterfaceLevelFollowControlAsync() throws InterruptedException {
+ when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
+ initFlowRule(invoker.getUrl().getColonSeparatedKey());
+ Result result1 = responseBack(requestGo(false, invocation));
+ assertEquals("normal", result1.getValue());
+ // should fallback because the qps > 1
+ Result result2 = responseBack(requestGo(false, invocation));
+ assertEquals("fallback", result2.getValue());
+ // sleeping 1000 ms to reset qps
+ Thread.sleep(1000);
+ Result result3 = responseBack(requestGo(false, invocation));
+ assertEquals("normal", result3.getValue());
+
+ verifyInvocationStructureForCallFinish();
}
@Test
- public void testInvoke() {
- final Invoker invoker = mock(Invoker.class);
- when(invoker.getInterface()).thenReturn(DemoService.class);
+ public void testDegradeAsync() throws InterruptedException {
+ when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
+
+ initDegradeRule(invoker.getUrl().getColonSeparatedKey());
+ Result result = requestGo(false, invocation);
+ verifyInvocationStructureForAsyncCall(invoker, invocation);
+ responseBack(result);
+ assertEquals("normal", result.getValue());
+ // inc the clusterNode's exception to trigger the fallback
+ for (int i = 0; i < 5; i++) {
+ responseBack(requestGo(true, invocation));
+ verifyInvocationStructureForCallFinish();
+ }
+ Result result2 = responseBack(requestGo(false, invocation));
+ assertEquals("fallback", result2.getValue());
+ // sleeping 1000 ms to reset exception
+ Thread.sleep(1000);
+
+ Result result3 = responseBack(requestGo(false, invocation));
+ assertEquals("normal", result3.getValue());
+
+ Context context = ContextUtil.getContext();
+ assertNull(context);
+ }
+
+ @Test
+ public void testDegradeSync() throws InterruptedException {
+
+ initDegradeRule(invoker.getUrl().getColonSeparatedKey());
+ Result result = requestGo(false, invocation);
+ verifyInvocationStructure(invoker, invocation);
+ responseBack(result);
+ assertEquals("normal", result.getValue());
+ // inc the clusterNode's exception to trigger the fallback
+ for (int i = 0; i < 5; i++) {
+ responseBack(requestGo(true, invocation));
+ verifyInvocationStructureForCallFinish();
+ }
+ Result result2 = responseBack(requestGo(false, invocation));
+ assertEquals("fallback", result2.getValue());
+ // sleeping 1000 ms to reset exception
+ Thread.sleep(1000);
+
+ Result result3 = responseBack(requestGo(false, invocation));
+ assertEquals("normal", result3.getValue());
+
+ Context context = ContextUtil.getContext();
+ assertNull(context);
+ }
- final Invocation invocation = mock(Invocation.class);
- Method method = DemoService.class.getMethods()[0];
- when(invocation.getMethodName()).thenReturn(method.getName());
- when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
+
+ @Test
+ public void testMethodFlowControlAsync() {
+ when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
+ initFlowRule(DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()));
+ responseBack(requestGo(false, invocation));
+
+ responseBack(requestGo(false, invocation));
+
+ Invocation invocation2 = mock(Invocation.class);
+ Method method = DemoService.class.getMethods()[1];
+ when(invocation2.getMethodName()).thenReturn(method.getName());
+ when(invocation2.getParameterTypes()).thenReturn(method.getParameterTypes());
+ Result result2 = responseBack(requestGo(false, invocation2));
+ verifyInvocationStructureForCallFinish();
+ assertEquals("normal", result2.getValue());
+
+ // the method of invocation should be blocked
+ Result fallback = requestGo(false, invocation);
+ assertNotNull(RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY));
+ assertNull(RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY));
+ responseBack(fallback);
+ assertEquals("fallback", fallback.getValue());
+ verifyInvocationStructureForCallFinish();
+
+
+ }
+
+ public Result requestGo(boolean exception, Invocation currentInvocation) {
+ AsyncRpcResult result = null;
+
+ if (exception) {
+ result = AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), currentInvocation);
+ } else {
+ result = AsyncRpcResult.newDefaultAsyncResult("normal", currentInvocation);
+ }
+ when(invoker.invoke(currentInvocation)).thenReturn(result);
+ return filter.invoke(invoker, currentInvocation);
+ }
+
+ public Result responseBack(Result result) {
+ filter.listener().onResponse(result, invoker, invocation);
+ return result;
+ }
+
+
+ @Test
+ public void testInvokeAsync() throws InterruptedException {
+
+ when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString());
final Result result = mock(Result.class);
when(result.hasException()).thenReturn(false);
+ when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
+ verifyInvocationStructureForAsyncCall(invoker, invocation);
+ return result;
+ });
+
+ filter.invoke(invoker, invocation);
+ verify(invoker).invoke(invocation);
+
+ Context context = ContextUtil.getContext();
+ assertNotNull(context);
+ }
+
+ @Test
+ public void testInvokeSync() {
+
+ final Result result = mock(Result.class);
+ when(result.hasException()).thenReturn(false);
+ when(result.getException()).thenReturn(new Exception());
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
verifyInvocationStructure(invoker, invocation);
return result;
@@ -79,6 +265,7 @@ public void testInvoke() {
filter.invoke(invoker, invocation);
verify(invoker).invoke(invocation);
+ filter.listener().onResponse(result, invoker, invocation);
Context context = ContextUtil.getContext();
assertNull(context);
}
@@ -92,34 +279,35 @@ public void testInvoke() {
private void verifyInvocationStructure(Invoker invoker, Invocation invocation) {
Context context = ContextUtil.getContext();
assertNotNull(context);
-
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context
// In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter
// If consumer is on the top of Dubbo RPC invocation chain, use default context
- String resourceName = DubboUtils.getResourceName(invoker, invocation);
- assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName());
+ String resourceName = DubboUtils.getResourceName(invoker, invocation, true);
+ assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName());
assertEquals("", context.getOrigin());
DefaultNode entranceNode = context.getEntranceNode();
ResourceWrapper entranceResource = entranceNode.getId();
- assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
- assertSame(EntryType.IN, entranceResource.getType());
+
+ assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
+ assertSame(EntryType.IN, entranceResource.getEntryType());
// As SphU.entry(interfaceName, EntryType.OUT);
Set childList = entranceNode.getChildList();
assertEquals(1, childList.size());
- DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
+ DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode);
ResourceWrapper interfaceResource = interfaceNode.getId();
- assertEquals(DemoService.class.getName(), interfaceResource.getName());
- assertSame(EntryType.OUT, interfaceResource.getType());
+
+ assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName());
+ assertSame(EntryType.OUT, interfaceResource.getEntryType());
// As SphU.entry(resourceName, EntryType.OUT);
childList = interfaceNode.getChildList();
assertEquals(1, childList.size());
- DefaultNode methodNode = (DefaultNode) childList.iterator().next();
+ DefaultNode methodNode = getNode(resourceName, entranceNode);
ResourceWrapper methodResource = methodNode.getId();
assertEquals(resourceName, methodResource.getName());
- assertSame(EntryType.OUT, methodResource.getType());
+ assertSame(EntryType.OUT, methodResource.getEntryType());
// Verify curEntry
Entry curEntry = context.getCurEntry();
@@ -139,4 +327,82 @@ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) {
Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
assertEquals(0, interfaceOriginCountMap.size());
}
+
+ private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation invocation) {
+ Context context = ContextUtil.getContext();
+ assertNotNull(context);
+
+ // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context
+ // In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter
+ // If consumer is on the top of Dubbo RPC invocation chain, use default context
+ String resourceName = DubboUtils.getResourceName(invoker, invocation, true);
+ assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName());
+ assertEquals("", context.getOrigin());
+
+ DefaultNode entranceNode = context.getEntranceNode();
+ ResourceWrapper entranceResource = entranceNode.getId();
+ assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
+ assertSame(EntryType.IN, entranceResource.getEntryType());
+
+ // As SphU.entry(interfaceName, EntryType.OUT);
+ Set childList = entranceNode.getChildList();
+ assertEquals(2, childList.size());
+ DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode);
+ ResourceWrapper interfaceResource = interfaceNode.getId();
+ assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName());
+ assertSame(EntryType.OUT, interfaceResource.getEntryType());
+
+ // As SphU.entry(resourceName, EntryType.OUT);
+ childList = interfaceNode.getChildList();
+ assertEquals(0, childList.size());
+ DefaultNode methodNode = getNode(resourceName, entranceNode);
+ ResourceWrapper methodResource = methodNode.getId();
+ assertEquals(resourceName, methodResource.getName());
+ assertSame(EntryType.OUT, methodResource.getEntryType());
+
+ // Verify curEntry
+ // nothing will bind to local context when use the AsyncEntry
+ Entry curEntry = context.getCurEntry();
+ assertNull(curEntry);
+
+ // Verify clusterNode
+ ClusterNode methodClusterNode = methodNode.getClusterNode();
+ ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
+ assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode
+
+ // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
+ Map methodOriginCountMap = methodClusterNode.getOriginCountMap();
+ assertEquals(0, methodOriginCountMap.size());
+
+ Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
+ assertEquals(0, interfaceOriginCountMap.size());
+ }
+
+
+ private void verifyInvocationStructureForCallFinish() {
+ Context context = ContextUtil.getContext();
+ assertNull(context);
+ Entry interfaceEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY);
+ Entry methodEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY);
+ assertNull(interfaceEntry);
+ assertNull(methodEntry);
+ }
+
+
+ public DefaultNode getNode(String resourceName, DefaultNode root) {
+
+ Queue queue = new LinkedList<>();
+ queue.offer(root);
+ while (!queue.isEmpty()) {
+ DefaultNode temp = queue.poll();
+ if (temp.getId().getName().equals(resourceName)) {
+ return temp;
+ }
+ for (Node node : temp.getChildList()) {
+ queue.offer((DefaultNode) node);
+ }
+ }
+ return null;
+ }
+
}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java
index 8b0ab899a9..1a7000f1d6 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java
@@ -26,7 +26,8 @@
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.node.StatisticNode;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
-
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
@@ -38,8 +39,15 @@
import java.util.Map;
import java.util.Set;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* @author cdfive
@@ -50,6 +58,7 @@ public class SentinelDubboProviderFilterTest extends BaseTest {
@Before
public void setUp() {
+ constructInvokerAndInvocation();
cleanUpAll();
}
@@ -62,18 +71,16 @@ public void cleanUp() {
public void testInvoke() {
final String originApplication = "consumerA";
- final Invoker invoker = mock(Invoker.class);
- when(invoker.getInterface()).thenReturn(DemoService.class);
+ URL url = invoker.getUrl()
+ .addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE);
+ when(invoker.getUrl()).thenReturn(url);
- final Invocation invocation = mock(Invocation.class);
- Method method = DemoService.class.getMethods()[0];
- when(invocation.getMethodName()).thenReturn(method.getName());
- when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes());
when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""))
- .thenReturn(originApplication);
+ .thenReturn(originApplication);
final Result result = mock(Result.class);
when(result.hasException()).thenReturn(false);
+ when(result.getException()).thenReturn(new Exception());
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> {
verifyInvocationStructure(originApplication, invoker, invocation);
return result;
@@ -82,6 +89,7 @@ public void testInvoke() {
filter.invoke(invoker, invocation);
verify(invoker).invoke(invocation);
+ filter.listener().onResponse(result, invoker, invocation);
Context context = ContextUtil.getContext();
assertNull(context);
}
@@ -97,22 +105,23 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker
assertNotNull(context);
// As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter
- String resourceName = DubboUtils.getResourceName(invoker, invocation);
+ String resourceName = DubboUtils.getResourceName(invoker, invocation, true);
assertEquals(resourceName, context.getName());
assertEquals(originApplication, context.getOrigin());
DefaultNode entranceNode = context.getEntranceNode();
ResourceWrapper entranceResource = entranceNode.getId();
assertEquals(resourceName, entranceResource.getName());
- assertSame(EntryType.IN, entranceResource.getType());
+ assertSame(EntryType.IN, entranceResource.getEntryType());
// As SphU.entry(interfaceName, EntryType.IN);
Set childList = entranceNode.getChildList();
assertEquals(1, childList.size());
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
ResourceWrapper interfaceResource = interfaceNode.getId();
- assertEquals(DemoService.class.getName(), interfaceResource.getName());
- assertSame(EntryType.IN, interfaceResource.getType());
+
+ assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName());
+ assertSame(EntryType.IN, interfaceResource.getEntryType());
// As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
childList = interfaceNode.getChildList();
@@ -120,7 +129,7 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
ResourceWrapper methodResource = methodNode.getId();
assertEquals(resourceName, methodResource.getName());
- assertSame(EntryType.IN, methodResource.getType());
+ assertSame(EntryType.IN, methodResource.getEntryType());
// Verify curEntry
Entry curEntry = context.getCurEntry();
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java
index bc06ebf179..e1f199b729 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java
@@ -18,10 +18,11 @@
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
-
+import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Result;
-import org.apache.dubbo.rpc.RpcResult;
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
/**
@@ -29,6 +30,16 @@
*/
public class DubboFallbackRegistryTest {
+ @Before
+ public void setUp() {
+ DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback());
+ }
+
+ @After
+ public void tearDown() {
+ DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback());
+ }
+
@Test(expected = SentinelRpcException.class)
public void testDefaultFallback() {
// Test for default.
@@ -41,7 +52,7 @@ public void testDefaultFallback() {
public void testCustomFallback() {
BlockException ex = new FlowException("xxx");
DubboFallbackRegistry.setConsumerFallback(
- (invoker, invocation, e) -> new RpcResult("Error: " + e.getClass().getName()));
+ (invoker, invocation, e) -> AsyncRpcResult.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation));
Result result = DubboFallbackRegistry.getConsumerFallback()
.handle(null, null, ex);
Assert.assertFalse("The invocation should not fail", result.hasException());
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java
index 2a024cc9cb..26e8447997 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java
@@ -20,4 +20,5 @@
*/
public interface DemoService {
String sayHello(String name, int n);
+ String sayHi(String name,int n);
}
diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java
index f804c2c42e..b09e08bea9 100644
--- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java
+++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java
@@ -24,4 +24,9 @@ public class DemoServiceImpl implements DemoService {
public String sayHello(String name, int n) {
return "Hello " + name + ", " + n;
}
+
+ @Override
+ public String sayHi(String name, int n) {
+ return "Hi " + name + ", " + n;
+ }
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml
index 5cc4a43f06..6b2d57dcd3 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml
@@ -5,7 +5,7 @@
sentinel-adaptercom.alibaba.csp
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOT4.0.0
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java
index 2c56e6b7ce..5e8f78e2a7 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java
@@ -30,16 +30,22 @@ public final class SentinelGatewayConstants {
public static final int PARAM_PARSE_STRATEGY_HOST = 1;
public static final int PARAM_PARSE_STRATEGY_HEADER = 2;
public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3;
+ public static final int PARAM_PARSE_STRATEGY_COOKIE = 4;
+
+ public static final int URL_MATCH_STRATEGY_EXACT = 0;
+ public static final int URL_MATCH_STRATEGY_PREFIX = 1;
+ public static final int URL_MATCH_STRATEGY_REGEX = 2;
public static final int PARAM_MATCH_STRATEGY_EXACT = 0;
public static final int PARAM_MATCH_STRATEGY_PREFIX = 1;
public static final int PARAM_MATCH_STRATEGY_REGEX = 2;
+ public static final int PARAM_MATCH_STRATEGY_CONTAINS = 3;
public static final String GATEWAY_CONTEXT_DEFAULT = "sentinel_gateway_context_default";
public static final String GATEWAY_CONTEXT_PREFIX = "sentinel_gateway_context$$";
public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$";
- public static final String GATEWAY_NOT_MATCH_PARAM = "$$not_match";
+ public static final String GATEWAY_NOT_MATCH_PARAM = "$NM";
public static final String GATEWAY_DEFAULT_PARAM = "$D";
private SentinelGatewayConstants() {}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPathPredicateItem.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPathPredicateItem.java
index 1cbce589fb..a47b280154 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPathPredicateItem.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/ApiPathPredicateItem.java
@@ -17,6 +17,8 @@
import java.util.Objects;
+import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
+
/**
* @author Eric Zhao
* @since 1.6.0
@@ -24,7 +26,7 @@
public class ApiPathPredicateItem implements ApiPredicateItem {
private String pattern;
- private int matchStrategy;
+ private int matchStrategy = SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT;
public ApiPathPredicateItem setPattern(String pattern) {
this.pattern = pattern;
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java
index 963561f82a..d480512c28 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java
@@ -15,19 +15,24 @@
*/
package com.alibaba.csp.sentinel.adapter.gateway.common.command;
-import java.net.URLDecoder;
-import java.util.HashSet;
-import java.util.List;
-
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
+import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
+import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
+import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.net.URLDecoder;
+import java.util.HashSet;
+import java.util.Set;
/**
* @author Eric Zhao
@@ -36,6 +41,8 @@
@CommandMapping(name = "gateway/updateApiDefinitions", desc = "")
public class UpdateGatewayApiDefinitionGroupCommandHandler implements CommandHandler {
+ private static WritableDataSource> apiDefinitionWds = null;
+
@Override
public CommandResponse handle(CommandRequest request) {
String data = request.getParam("data");
@@ -52,10 +59,67 @@ public CommandResponse handle(CommandRequest request) {
RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {0}", data);
String result = SUCCESS_MSG;
- List apiDefinitions = JSONArray.parseArray(data, ApiDefinition.class);
- GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<>(apiDefinitions));
+
+ Set apiDefinitions = parseJson(data);
+ GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
+ if (!writeToDataSource(apiDefinitionWds, apiDefinitions)) {
+ result = WRITE_DS_FAILURE_MSG;
+ }
return CommandResponse.ofSuccess(result);
}
private static final String SUCCESS_MSG = "success";
+ private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
+
+ /**
+ * Parse json data to set of {@link ApiDefinition}.
+ *
+ * Since the predicateItems of {@link ApiDefinition} is set of interface,
+ * here we parse predicateItems to {@link ApiPathPredicateItem} temporarily.
+ */
+ private Set parseJson(String data) {
+ Set apiDefinitions = new HashSet<>();
+ JSONArray array = JSON.parseArray(data);
+ for (Object obj : array) {
+ JSONObject o = (JSONObject)obj;
+ ApiDefinition apiDefinition = new ApiDefinition((o.getString("apiName")));
+ Set predicateItems = new HashSet<>();
+ JSONArray itemArray = o.getJSONArray("predicateItems");
+ if (itemArray != null) {
+ predicateItems.addAll(itemArray.toJavaList(ApiPathPredicateItem.class));
+ }
+ apiDefinition.setPredicateItems(predicateItems);
+ apiDefinitions.add(apiDefinition);
+ }
+
+ return apiDefinitions;
+ }
+
+ /**
+ * Write target value to given data source.
+ *
+ * @param dataSource writable data source
+ * @param value target value to save
+ * @param value type
+ * @return true if write successful or data source is empty; false if error occurs
+ */
+ private boolean writeToDataSource(WritableDataSource dataSource, T value) {
+ if (dataSource != null) {
+ try {
+ dataSource.write(value);
+ } catch (Exception e) {
+ RecordLog.warn("Write data source failed", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public synchronized static WritableDataSource> getWritableDataSource() {
+ return apiDefinitionWds;
+ }
+
+ public synchronized static void setWritableDataSource(WritableDataSource> apiDefinitionWds) {
+ UpdateGatewayApiDefinitionGroupCommandHandler.apiDefinitionWds = apiDefinitionWds;
+ }
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java
index 46e14209b4..b79b78f9cf 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java
@@ -15,19 +15,20 @@
*/
package com.alibaba.csp.sentinel.adapter.gateway.common.command;
-import java.net.URLDecoder;
-import java.util.HashSet;
-import java.util.List;
-
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
+import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
-import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+
+import java.net.URLDecoder;
+import java.util.Set;
/**
* @author Eric Zhao
@@ -35,6 +36,7 @@
*/
@CommandMapping(name = "gateway/updateRules", desc = "Update gateway rules")
public class UpdateGatewayRuleCommandHandler implements CommandHandler {
+ private static WritableDataSource> gatewayFlowWds = null;
@Override
public CommandResponse handle(CommandRequest request) {
@@ -52,10 +54,43 @@ public CommandResponse handle(CommandRequest request) {
RecordLog.info(String.format("[API Server] Receiving rule change (type: gateway rule): %s", data));
String result = SUCCESS_MSG;
- List flowRules = JSONArray.parseArray(data, GatewayFlowRule.class);
- GatewayRuleManager.loadRules(new HashSet<>(flowRules));
+ Set flowRules = JSON.parseObject(data, new TypeReference>() {
+ });
+ GatewayRuleManager.loadRules(flowRules);
+ if (!writeToDataSource(gatewayFlowWds, flowRules)) {
+ result = WRITE_DS_FAILURE_MSG;
+ }
return CommandResponse.ofSuccess(result);
}
+ /**
+ * Write target value to given data source.
+ *
+ * @param dataSource writable data source
+ * @param value target value to save
+ * @param value type
+ * @return true if write successful or data source is empty; false if error occurs
+ */
+ private boolean writeToDataSource(WritableDataSource dataSource, T value) {
+ if (dataSource != null) {
+ try {
+ dataSource.write(value);
+ } catch (Exception e) {
+ RecordLog.warn("Write data source failed", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public synchronized static WritableDataSource> getWritableDataSource() {
+ return gatewayFlowWds;
+ }
+
+ public synchronized static void setWritableDataSource(WritableDataSource> gatewayFlowWds) {
+ UpdateGatewayRuleCommandHandler.gatewayFlowWds = gatewayFlowWds;
+ }
+
private static final String SUCCESS_MSG = "success";
+ private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
}
\ No newline at end of file
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParser.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParser.java
index f326468b68..40bc92d71f 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParser.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParser.java
@@ -17,6 +17,7 @@
import java.util.HashSet;
import java.util.Set;
+import java.util.regex.Pattern;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
@@ -42,8 +43,8 @@ public GatewayParamParser(RequestItemParser requestItemParser) {
/**
* Parse parameters for given resource from the request entity on condition of the rule predicate.
*
- * @param resource valid resource name
- * @param request valid request
+ * @param resource valid resource name
+ * @param request valid request
* @param rulePredicate rule predicate indicating the rules to refer
* @return the parameter array
*/
@@ -92,6 +93,8 @@ private String parseInternal(GatewayParamFlowItem item, T request) {
return parseHeader(item, request);
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM:
return parseUrlParameter(item, request);
+ case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE:
+ return parseCookie(item, request);
default:
return null;
}
@@ -100,7 +103,7 @@ private String parseInternal(GatewayParamFlowItem item, T request) {
private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) {
String clientIp = requestItemParser.getRemoteAddress(request);
String pattern = item.getPattern();
- if (pattern == null) {
+ if (StringUtil.isEmpty(pattern)) {
return clientIp;
}
return parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern);
@@ -111,7 +114,7 @@ private String parseHeader(/*@Valid*/ GatewayParamFlowItem item, T request) {
String pattern = item.getPattern();
// TODO: what if the header has multiple values?
String headerValue = requestItemParser.getHeader(request, headerKey);
- if (pattern == null) {
+ if (StringUtil.isEmpty(pattern)) {
return headerValue;
}
// Match value according to regex pattern or exact mode.
@@ -121,7 +124,7 @@ private String parseHeader(/*@Valid*/ GatewayParamFlowItem item, T request) {
private String parseHost(/*@Valid*/ GatewayParamFlowItem item, T request) {
String pattern = item.getPattern();
String host = requestItemParser.getHeader(request, "Host");
- if (pattern == null) {
+ if (StringUtil.isEmpty(pattern)) {
return host;
}
// Match value according to regex pattern or exact mode.
@@ -132,7 +135,18 @@ private String parseUrlParameter(/*@Valid*/ GatewayParamFlowItem item, T request
String paramName = item.getFieldName();
String pattern = item.getPattern();
String param = requestItemParser.getUrlParam(request, paramName);
- if (pattern == null) {
+ if (StringUtil.isEmpty(pattern)) {
+ return param;
+ }
+ // Match value according to regex pattern or exact mode.
+ return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern);
+ }
+
+ private String parseCookie(/*@Valid*/ GatewayParamFlowItem item, T request) {
+ String cookieName = item.getFieldName();
+ String pattern = item.getPattern();
+ String param = requestItemParser.getCookieValue(request, cookieName);
+ if (StringUtil.isEmpty(pattern)) {
return param;
}
// Match value according to regex pattern or exact mode.
@@ -140,13 +154,22 @@ private String parseUrlParameter(/*@Valid*/ GatewayParamFlowItem item, T request
}
private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) {
- // TODO: implement here.
if (value == null) {
return null;
}
- if (matchStrategy == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) {
- return value;
+ switch (matchStrategy) {
+ case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT:
+ return value.equals(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
+ case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS:
+ return value.contains(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
+ case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX:
+ Pattern regex = GatewayRegexCache.getRegexPattern(pattern);
+ if (regex == null) {
+ return value;
+ }
+ return regex.matcher(value).matches() ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
+ default:
+ return value;
}
- return value;
}
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayRegexCache.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayRegexCache.java
new file mode 100644
index 0000000000..17d22b9e94
--- /dev/null
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayRegexCache.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.adapter.gateway.common.param;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ * @author Eric Zhao
+ * @since 1.6.2
+ */
+public final class GatewayRegexCache {
+
+ private static final Map REGEX_CACHE = new ConcurrentHashMap<>();
+
+ public static Pattern getRegexPattern(String pattern) {
+ if (pattern == null) {
+ return null;
+ }
+ return REGEX_CACHE.get(pattern);
+ }
+
+ public static boolean addRegexPattern(String pattern) {
+ if (pattern == null) {
+ return false;
+ }
+ try {
+ Pattern regex = Pattern.compile(pattern);
+ REGEX_CACHE.put(pattern, regex);
+ return true;
+ } catch (Exception ex) {
+ RecordLog.warn("[GatewayRegexCache] Failed to compile the regex: " + pattern, ex);
+ return false;
+ }
+ }
+
+ public static void clear() {
+ REGEX_CACHE.clear();
+ }
+
+ private GatewayRegexCache() {}
+}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/RequestItemParser.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/RequestItemParser.java
index 5b28abe785..7eb71c52be 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/RequestItemParser.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/RequestItemParser.java
@@ -41,7 +41,7 @@ public interface RequestItemParser {
* Get the header associated with the header key.
*
* @param request valid request
- * @param key valid header key
+ * @param key valid header key
* @return the header
*/
String getHeader(T request, String key);
@@ -49,9 +49,19 @@ public interface RequestItemParser {
/**
* Get the parameter value associated with the parameter name.
*
- * @param request valid request
+ * @param request valid request
* @param paramName valid parameter name
* @return the parameter value
*/
String getUrlParam(T request, String paramName);
+
+ /**
+ * Get the cookie value associated with the cookie name.
+ *
+ * @param request valid request
+ * @param cookieName valid cookie name
+ * @return the cookie value
+ * @since 1.7.0
+ */
+ String getCookieValue(T request, String cookieName);
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverter.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverter.java
index f5531fa24f..814c0bfe20 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverter.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleConverter.java
@@ -15,7 +15,9 @@
*/
package com.alibaba.csp.sentinel.adapter.gateway.common.rule;
+import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
/**
@@ -32,6 +34,18 @@ static FlowRule toFlowRule(/*@Valid*/ GatewayFlowRule rule) {
.setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs());
}
+ static ParamFlowItem generateNonMatchPassParamItem() {
+ return new ParamFlowItem().setClassType(String.class.getName())
+ .setCount(1000_0000)
+ .setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
+ }
+
+ static ParamFlowItem generateNonMatchBlockParamItem() {
+ return new ParamFlowItem().setClassType(String.class.getName())
+ .setCount(0)
+ .setObject(SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM);
+ }
+
static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
return new ParamFlowRule(gatewayRule.getResource())
.setCount(gatewayRule.getCount())
@@ -63,7 +77,11 @@ static ParamFlowRule applyToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, in
GatewayParamFlowItem gatewayItem = gatewayRule.getParamItem();
// Apply the current idx to gateway rule item.
gatewayItem.setIndex(idx);
- // TODO: implement for pattern-based parameters.
+ // Apply for pattern-based parameters.
+ String valuePattern = gatewayItem.getPattern();
+ if (valuePattern != null) {
+ paramRule.getParamFlowItemList().add(generateNonMatchPassParamItem());
+ }
return paramRule;
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManager.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManager.java
index e4880bed2d..8744707d85 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManager.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManager.java
@@ -16,6 +16,7 @@
package com.alibaba.csp.sentinel.adapter.gateway.common.rule;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -24,6 +25,7 @@
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
+import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayRegexCache;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
@@ -131,6 +133,16 @@ private int getIdxInternal(Map idxMap, String resourceName) {
return idxMap.get(resourceName);
}
+ private void cacheRegexPattern(/*@NonNull*/ GatewayParamFlowItem item) {
+ String pattern = item.getPattern();
+ if (StringUtil.isNotEmpty(pattern) &&
+ item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) {
+ if (GatewayRegexCache.getRegexPattern(pattern) == null) {
+ GatewayRegexCache.addRegexPattern(pattern);
+ }
+ }
+ }
+
private synchronized void applyGatewayRuleInternal(Set conf) {
if (conf == null || conf.isEmpty()) {
applyToConvertedParamMap(new HashSet());
@@ -162,6 +174,7 @@ private synchronized void applyGatewayRuleInternal(Set conf) {
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) {
idxMap.put(rule.getResource(), idx + 1);
}
+ cacheRegexPattern(rule.getParamItem());
}
// Apply to the gateway rule map.
Set ruleSet = gatewayRuleMap.get(resourceName);
@@ -242,14 +255,18 @@ static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) {
if (item.getParseStrategy() < 0) {
return false;
}
- if (item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM ||
- item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) {
- if (StringUtil.isBlank(item.getFieldName())) {
- return false;
- }
+ // Check required field name for item types.
+ if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) {
+ return false;
}
return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0;
}
+ private static final Set FIELD_REQUIRED_SET = new HashSet<>(
+ Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM,
+ SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER,
+ SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
+ );
+
private GatewayRuleManager() {}
}
diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java
index 7edc7844ed..e37c693018 100644
--- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java
+++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java
@@ -94,6 +94,7 @@ public void testParseParametersWithItems() {
final String api1 = "my_test_route_B";
final String headerName = "X-Sentinel-Flag";
final String paramName = "p";
+ final String cookieName = "myCookie";
GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1)
.setCount(10)
.setIntervalSec(10);
@@ -128,6 +129,13 @@ public void testParseParametersWithItems() {
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST)
);
+ GatewayFlowRule routeRule5 = new GatewayFlowRule(routeId1)
+ .setCount(50)
+ .setIntervalSec(30)
+ .setParamItem(new GatewayParamFlowItem()
+ .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE)
+ .setFieldName(cookieName)
+ );
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1)
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
@@ -140,6 +148,7 @@ public void testParseParametersWithItems() {
rules.add(routeRule2);
rules.add(routeRule3);
rules.add(routeRule4);
+ rules.add(routeRule5);
rules.add(routeRuleNoParam);
rules.add(apiRule1);
GatewayRuleManager.loadRules(rules);
@@ -148,19 +157,24 @@ public void testParseParametersWithItems() {
final String expectedAddress = "66.77.88.99";
final String expectedHeaderValue1 = "Sentinel";
final String expectedUrlParamValue1 = "17";
+ final String expectedCookieValue1 = "Sentinel-Foo";
+
mockClientHostAddress(itemParser, expectedAddress);
Map expectedHeaders = new HashMap() {{
put(headerName, expectedHeaderValue1); put("Host", expectedHost);
}};
mockHeaders(itemParser, expectedHeaders);
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1);
+ mockSingleCookie(itemParser, cookieName, expectedCookieValue1);
+
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate);
- // Param length should be 5 (4 with parameters, 1 normal flow with generated constant)
- assertThat(params.length).isEqualTo(5);
+ // Param length should be 6 (5 with parameters, 1 normal flow with generated constant)
+ assertThat(params.length).isEqualTo(6);
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress);
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1);
assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1);
assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost);
+ assertThat(params[routeRule5.getParamItem().getIndex()]).isEqualTo(expectedCookieValue1);
assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM);
assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero();
@@ -172,6 +186,125 @@ public void testParseParametersWithItems() {
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2);
}
+ @Test
+ public void testParseParametersWithEmptyItemPattern() {
+ RequestItemParser
+
+ org.assertj
+ assertj-core
+ test
+
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
index 657223e952..8dc51a5eec 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
@@ -15,16 +15,17 @@
*/
package com.alibaba.csp.sentinel.cluster.client.codec.data;
-import java.util.Collection;
-
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AssertUtil;
-
import io.netty.buffer.ByteBuf;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
/**
* @author jialiang.linjl
* @author Eric Zhao
@@ -50,41 +51,67 @@ public void writeTo(ParamFlowRequestData entity, ByteBuf target) {
Collection params = entity.getParams();
- // Write parameter amount.
- int amount = calculateParamAmount(params);
- target.writeInt(amount);
+ params = resolveValidParams(params);
+ target.writeInt(params.size());
// Serialize parameters with type flag.
- for (Object param : entity.getParams()) {
+ for (Object param : params) {
encodeValue(param, target);
}
}
+ /**
+ * Get valid parameters in provided parameter list
+ *
+ * @param params
+ * @return
+ */
+ public List resolveValidParams(Collection params) {
+ List validParams = new ArrayList<>();
+ int size = 0;
+ for (Object param : params) {
+ int s = calculateParamTransportSize(param);
+ if (s <= 0) {
+ RecordLog.warn("[ParamFlowRequestDataWriter] WARN: Non-primitive type detected in params of "
+ + "cluster parameter flow control, which is not supported: " + param);
+ continue;
+ }
+ if (size + s > maxParamByteSize) {
+ RecordLog.warn("[ParamFlowRequestDataWriter] WARN: params size is too big." +
+ " the configure value is : " + maxParamByteSize + ", the params size is: " + params.size());
+ break;
+ }
+ size += s;
+ validParams.add(param);
+ }
+ return validParams;
+ }
+
private void encodeValue(Object param, ByteBuf target) {
// Handle primitive type.
if (param instanceof Integer || int.class.isInstance(param)) {
target.writeByte(ClusterConstants.PARAM_TYPE_INTEGER);
- target.writeInt((Integer)param);
+ target.writeInt((Integer) param);
} else if (param instanceof String) {
- encodeString((String)param, target);
+ encodeString((String) param, target);
} else if (boolean.class.isInstance(param) || param instanceof Boolean) {
target.writeByte(ClusterConstants.PARAM_TYPE_BOOLEAN);
- target.writeBoolean((Boolean)param);
+ target.writeBoolean((Boolean) param);
} else if (long.class.isInstance(param) || param instanceof Long) {
target.writeByte(ClusterConstants.PARAM_TYPE_LONG);
- target.writeLong((Long)param);
+ target.writeLong((Long) param);
} else if (double.class.isInstance(param) || param instanceof Double) {
target.writeByte(ClusterConstants.PARAM_TYPE_DOUBLE);
- target.writeDouble((Double)param);
+ target.writeDouble((Double) param);
} else if (float.class.isInstance(param) || param instanceof Float) {
target.writeByte(ClusterConstants.PARAM_TYPE_FLOAT);
- target.writeFloat((Float)param);
+ target.writeFloat((Float) param);
} else if (byte.class.isInstance(param) || param instanceof Byte) {
target.writeByte(ClusterConstants.PARAM_TYPE_BYTE);
- target.writeByte((Byte)param);
+ target.writeByte((Byte) param);
} else if (short.class.isInstance(param) || param instanceof Short) {
target.writeByte(ClusterConstants.PARAM_TYPE_SHORT);
- target.writeShort((Short)param);
+ target.writeShort((Short) param);
} else {
// Unexpected type, drop.
}
@@ -97,30 +124,6 @@ private void encodeString(String param, ByteBuf target) {
target.writeBytes(tmpChars);
}
- /**
- * Calculate amount of valid parameters in provided parameter list.
- *
- * @param params non-empty parameter list
- * @return amount of valid parameters
- */
- int calculateParamAmount(/*@NonEmpty*/ Collection params) {
- int size = 0;
- int length = 0;
- for (Object param : params) {
- int s = calculateParamTransportSize(param);
- if (s <= 0) {
- RecordLog.warn("[ParamFlowRequestDataWriter] WARN: Non-primitive type detected in params of "
- + "cluster parameter flow control, which is not supported: " + param);
- continue;
- }
- if (size + s > maxParamByteSize) {
- break;
- }
- size += s;
- length++;
- }
- return length;
- }
int calculateParamTransportSize(Object value) {
if (value == null) {
@@ -132,7 +135,7 @@ int calculateParamTransportSize(Object value) {
return 5;
} else if (value instanceof String) {
// Layout for string: |type flag(1)|length(4)|string content|
- String tmpValue = (String)value;
+ String tmpValue = (String) value;
byte[] tmpChars = tmpValue.getBytes();
return 1 + 4 + tmpChars.length;
} else if (boolean.class.isInstance(value) || value instanceof Boolean) {
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java
index e12395f0de..73c4a62bcb 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java
@@ -27,9 +27,14 @@ public class PingResponseDataDecoder implements EntityDecoder
@Override
public Integer decode(ByteBuf source) {
- if (source.readableBytes() >= 1) {
+ int size = source.readableBytes();
+ if (size == 1) {
+ // Compatible with old version (< 1.7.0).
return (int) source.readByte();
}
+ if (size >= 4) {
+ return source.readInt();
+ }
return -1;
}
}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientStartUpConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientStartUpConfig.java
new file mode 100644
index 0000000000..8da2813a1c
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientStartUpConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.config;
+
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ *
+ * this class dedicated to reading startup configurations of cluster client
+ *
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class ClusterClientStartUpConfig {
+
+ private static final String MAX_PARAM_BYTE_SIZE = "csp.sentinel.cluster.max.param.byte.size";
+
+ /**
+ * Get the max bytes params can be serialized
+ *
+ * @return the max bytes, may be null
+ */
+ public static Integer getMaxParamByteSize() {
+ String maxParamByteSize = SentinelConfig.getConfig(MAX_PARAM_BYTE_SIZE);
+ try {
+ return maxParamByteSize == null ? null : Integer.valueOf(maxParamByteSize);
+ } catch (Exception ex) {
+ RecordLog.warn("[ClusterClientStartUpConfig] Failed to parse maxParamByteSize: " + maxParamByteSize);
+ return null;
+ }
+ }
+
+
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java
index d5851bc01f..5b9467c7d5 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java
@@ -23,6 +23,7 @@
import com.alibaba.csp.sentinel.cluster.client.codec.data.PingResponseDataDecoder;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry;
import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientStartUpConfig;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
@@ -42,7 +43,12 @@ public void init() throws Exception {
private void initDefaultEntityWriters() {
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PING, new PingRequestDataWriter());
RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_FLOW, new FlowRequestDataWriter());
- RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter());
+ Integer maxParamByteSize = ClusterClientStartUpConfig.getMaxParamByteSize();
+ if (maxParamByteSize == null) {
+ RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter());
+ } else {
+ RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter(maxParamByteSize));
+ }
}
private void initDefaultEntityDecoders() {
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriterTest.java b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriterTest.java
index 314020203c..de74dd8b8a 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriterTest.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriterTest.java
@@ -1,9 +1,10 @@
package com.alibaba.csp.sentinel.cluster.client.codec.data;
-import java.util.ArrayList;
-
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.junit.Assert.*;
/**
@@ -27,35 +28,33 @@ public void testCalculateParamTransportSize() {
}
@Test
- public void testCalculateParamAmountExceedsMaxSize() {
- final int maxSize = 10;
+ public void testResolveValidParams() {
+
+ final int maxSize = 15;
ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter(maxSize);
- assertEquals(1, writer.calculateParamAmount(new ArrayList() {{
+
+ ArrayList params = new ArrayList() {{
add(1);
- }}));
- assertEquals(2, writer.calculateParamAmount(new ArrayList() {{
- add(1); add(64);
- }}));
- assertEquals(2, writer.calculateParamAmount(new ArrayList() {{
- add(1); add(64); add(3);
- }}));
- }
+ add(64);
+ add(3);
+ }};
- @Test
- public void testCalculateParamAmount() {
- ParamFlowRequestDataWriter writer = new ParamFlowRequestDataWriter();
- assertEquals(6, writer.calculateParamAmount(new ArrayList() {{
- add(1); add(1d); add(1f); add((byte) 1); add("123"); add(true);
- }}));
- // POJO (non-primitive type) should not be regarded as a valid parameter.
- assertEquals(0, writer.calculateParamAmount(new ArrayList() {{
+ List validParams = writer.resolveValidParams(params);
+ assertTrue(validParams.contains(1) && validParams.contains(64) && validParams.contains(3));
+
+ //when over maxSize, the exceed number should not be contained
+ params.add(5);
+ assertFalse(writer.resolveValidParams(params).contains(5));
+
+
+ //POJO (non-primitive type) should not be regarded as a valid parameter
+ assertTrue(writer.resolveValidParams(new ArrayList() {{
add(new SomePojo());
- }}));
- assertEquals(1, writer.calculateParamAmount(new ArrayList() {{
- add(new Object()); add(1);
- }}));
+ }}).size() == 0);
+
}
+
private static class SomePojo {
private String param1;
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoderTest.java b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoderTest.java
new file mode 100644
index 0000000000..f2b04ec662
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoderTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.data;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Eric Zhao
+ */
+public class PingResponseDataDecoderTest {
+
+ @Test
+ public void testDecodePingResponseData() {
+ ByteBuf buf = Unpooled.buffer();
+ PingResponseDataDecoder decoder = new PingResponseDataDecoder();
+
+ int big = Integer.MAX_VALUE;
+ buf.writeInt(big);
+ assertThat(decoder.decode(buf)).isEqualTo(big);
+
+ byte small = 12;
+ buf.writeByte(small);
+ assertThat(decoder.decode(buf)).isEqualTo(small);
+
+ buf.release();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/pom.xml b/sentinel-cluster/sentinel-cluster-common-default/pom.xml
index cc1a988060..59549847b3 100644
--- a/sentinel-cluster/sentinel-cluster-common-default/pom.xml
+++ b/sentinel-cluster/sentinel-cluster-common-default/pom.xml
@@ -5,7 +5,7 @@
sentinel-clustercom.alibaba.csp
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOT4.0.0
diff --git a/sentinel-cluster/sentinel-cluster-server-default/pom.xml b/sentinel-cluster/sentinel-cluster-server-default/pom.xml
index 6522b710fb..6443bab92d 100644
--- a/sentinel-cluster/sentinel-cluster-server-default/pom.xml
+++ b/sentinel-cluster/sentinel-cluster-server-default/pom.xml
@@ -5,7 +5,7 @@
sentinel-clustercom.alibaba.csp
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOT4.0.0
@@ -52,5 +52,10 @@
mockito-coretest
+
+ org.assertj
+ assertj-core
+ test
+
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
index ab1147999d..df69bef9dc 100644
--- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
@@ -64,7 +64,7 @@ static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCoun
return new TokenResult(TokenResultStatus.FAIL);
}
- double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST);
+ double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount();
double nextRemaining = globalThreshold - latestQps - acquireCount;
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
index 969e540037..1f9cd1c785 100644
--- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
@@ -15,13 +15,10 @@
*/
package com.alibaba.csp.sentinel.cluster.server;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ServiceLoader;
-
import com.alibaba.csp.sentinel.cluster.TokenService;
import com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService;
import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.SpiLoader;
/**
* @author Eric Zhao
@@ -31,8 +28,6 @@ public final class TokenServiceProvider {
private static TokenService service = null;
- private static final ServiceLoader LOADER = ServiceLoader.load(TokenService.class);
-
static {
resolveTokenServiceSpi();
}
@@ -42,24 +37,12 @@ public static TokenService getService() {
}
private static void resolveTokenServiceSpi() {
- boolean hasOther = false;
- List list = new ArrayList();
- for (TokenService service : LOADER) {
- if (service.getClass() != DefaultTokenService.class) {
- hasOther = true;
- list.add(service);
- }
- }
-
- if (hasOther) {
- // Pick the first.
- service = list.get(0);
+ service = SpiLoader.loadFirstInstanceOrDefault(TokenService.class, DefaultTokenService.class);
+ if (service != null) {
+ RecordLog.info("[TokenServiceProvider] Global token service resolved: "
+ + service.getClass().getCanonicalName());
} else {
- // No custom token service, using default.
- service = new DefaultTokenService();
+ RecordLog.warn("[TokenServiceProvider] Unable to resolve TokenService: no SPI found");
}
-
- RecordLog.info("[TokenServiceProvider] Global token service resolved: "
- + service.getClass().getCanonicalName());
}
}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java
index 26e31ecd2f..f380b61dd3 100644
--- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java
@@ -31,6 +31,6 @@ public void writeTo(Integer entity, ByteBuf target) {
if (entity == null || target == null) {
return;
}
- target.writeByte(entity);
+ target.writeInt(entity);
}
}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java
index bd572f27c5..ca1bd87d52 100644
--- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java
@@ -20,6 +20,7 @@
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.cluster.annotation.RequestType;
+import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
@@ -30,7 +31,8 @@ public final class RequestProcessorProvider {
private static final Map PROCESSOR_MAP = new ConcurrentHashMap<>();
- private static final ServiceLoader SERVICE_LOADER = ServiceLoader.load(RequestProcessor.class);
+ private static final ServiceLoader SERVICE_LOADER = ServiceLoaderUtil.getServiceLoader(
+ RequestProcessor.class);
static {
loadAndInit();
@@ -71,6 +73,5 @@ static void addProcessor(int type, RequestProcessor processor) {
PROCESSOR_MAP.put(type, processor);
}
-
private RequestProcessorProvider() {}
}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriterTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriterTest.java
new file mode 100644
index 0000000000..96191cfa15
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.data;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test cases for {@link PingResponseDataWriter}.
+ *
+ * @author Eric Zhao
+ */
+public class PingResponseDataWriterTest {
+
+ @Test
+ public void testWritePingResponseAndParse() {
+ ByteBuf buf = Unpooled.buffer();
+ PingResponseDataWriter writer = new PingResponseDataWriter();
+
+ int small = 120;
+ writer.writeTo(small, buf);
+ assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4);
+ assertThat(buf.readInt()).isEqualTo(small);
+
+ int big = Integer.MAX_VALUE;
+ writer.writeTo(big, buf);
+ assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4);
+ assertThat(buf.readInt()).isEqualTo(big);
+
+ buf.release();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/Dockerfile b/sentinel-cluster/sentinel-cluster-server-envoy-rls/Dockerfile
new file mode 100644
index 0000000000..7a1f24408b
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/Dockerfile
@@ -0,0 +1,10 @@
+FROM java:8-jre
+
+ENV SENTINEL_HOME /sentinel
+
+COPY target/sentinel-envoy-rls-token-server.jar $SENTINEL_HOME/
+
+WORKDIR $SENTINEL_HOME
+
+ENTRYPOINT ["sh", "-c"]
+CMD ["java -Dcsp.sentinel.log.dir=/sentinel/logs/ ${JAVA_OPTS} -jar sentinel-envoy-rls-token-server.jar"]
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md b/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md
new file mode 100644
index 0000000000..e610043686
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md
@@ -0,0 +1,59 @@
+# Sentinel Token Server (Envoy RLS implementation)
+
+This module provides the [Envoy rate limiting gRPC service](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting#arch-overview-rate-limit) implementation
+with Sentinel token server.
+
+> Note: the gRPC stub classes for Envoy RLS service is generated via `protobuf-maven-plugin` during the `compile` goal.
+> The generated classes is located in the directory: `target/generated-sources/protobuf`.
+
+## Build
+
+Build the executable jar:
+
+```bash
+mvn clean package -P prod
+```
+
+## Rule configuration
+
+Currently Sentinel RLS token server supports dynamic rule configuration via the yaml file.
+The file may provide rules for one *domain* (defined in Envoy's conf file).
+In Envoy, one rate limit request might carry multiple *rate limit descriptors*
+(which will be generated from [Envoy rate limit actions](https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-msg-route-ratelimit)).
+One rate limit descriptor may have multiple entries (key-value pair).
+We may set different threshold for each rate limit descriptors.
+
+A sample rule configuration file:
+
+```yaml
+domain: foo
+descriptors:
+ - resources:
+ - key: "destination_cluster"
+ value: "service_httpbin"
+ count: 1
+```
+
+This rule only takes effect for domain `foo`. It will limit the max QPS to 1 for
+all requests targeted to the `service_httpbin` cluster.
+
+We need to provide the path to yaml file via the `SENTINEL_RLS_RULE_FILE_PATH` env
+(or `-Dcsp.sentinel.rls.rule.file` opts). Then as soon as the content in the rule file has been changed,
+Sentinel will reload the new rules from the file to the `EnvoyRlsRuleManager`.
+
+We may check the logs in `~/logs/csp/sentinel-record.log.xxx` to see whether the rules has been loaded.
+We may also retrieve the converted `FlowRule` via the command API `localhost:8719/cluster/server/flowRules`.
+
+## Configuration items
+
+The configuration list:
+
+| Item (env) | Item (JVM property) | Description | Default Value | Required |
+|--------|--------|--------|--------|--------|
+| `SENTINEL_RLS_GRPC_PORT` | `csp.sentinel.grpc.server.port` | The RLS gRPC server port | **10240** | false |
+| `SENTINEL_RLS_RULE_FILE_PATH` | `csp.sentinel.rls.rule.file` | The path of the RLS rule yaml file | - | **true** |
+| `SENTINEL_RLS_ACCESS_LOG` | - | Whether to enable the access log (`on` for enable) | off | false |
+
+## Samples
+
+- [Kubernetes sample](./sample/k8s)
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml b/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml
new file mode 100644
index 0000000000..3c84641c79
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml
@@ -0,0 +1,156 @@
+
+
+
+ sentinel-cluster
+ com.alibaba.csp
+ 1.7.2-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-cluster-server-envoy-rls
+
+
+ 1.8
+ 1.8
+
+ 3.10.0
+ 1.24.0
+
+ 3.2.1
+
+
+
+
+ com.alibaba.csp
+ sentinel-cluster-server-default
+ ${project.version}
+
+
+ com.alibaba.csp
+ sentinel-datasource-extension
+
+
+ com.alibaba.csp
+ sentinel-transport-simple-http
+
+
+
+ io.grpc
+ grpc-netty
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc.version}
+
+
+ com.google.protobuf
+ protobuf-java
+ ${protobuf.version}
+
+
+
+ org.yaml
+ snakeyaml
+ 1.25
+
+
+
+ junit
+ junit
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.6.2
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ 0.6.1
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ ${maven.pmd.version}
+
+
+ target/generated-sources
+
+
+
+
+
+
+
+
+ prod
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven.shade.version}
+
+
+ package
+
+ shade
+
+
+ sentinel-envoy-rls-token-server
+
+
+
+ com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsServer
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/README.md b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/README.md
new file mode 100644
index 0000000000..3fd58ba887
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/README.md
@@ -0,0 +1,105 @@
+# Sentinel Envoy RLS - Kubernetes sample
+
+This sample will illustrate how to use Sentinel RLS token server with Envoy in Kubernetes clusters.
+
+## Build the Docker image
+
+We could use the pre-built Docker image: `registry.cn-hangzhou.aliyuncs.com/sentinel-docker-repo/sentinel-envoy-rls-server:latest`
+
+We can also manually build the Docker image in the `sentinel-cluster-server-envoy-rls` directory:
+
+```bash
+docker build -t "sentinel/sentinel-envoy-rls-server:latest" -f ./Dockerfile .
+```
+
+## Deploy Sentinel RLS token server
+
+Next we could deploy the Sentinel RLS token server in the K8S cluster.
+We've provided a deployment template for Sentinel RLS token server in `sentinel-rls.yml`.
+It includes:
+
+- A `ConfigMap` that contains the cluster flow control rule for Envoy global rate limiting.
+ This will be mounted as a file in the target `Deployment`, so that the Sentinel RLS token server
+ could load the rules dynamically as soon as the rule in the `ConfigMap` has been updated.
+- A `Deployment` for Sentinel RLS token server. It will mount the `ConfigMap` as a volume
+ for dynamic rule configuration.
+- A `Service` that exports the Sentinel command port (8719) and the RLS gRPC port (by default 10245)
+ on a cluster-internal IP so that the Envoy pods could communicate with the RLS server.
+
+The sample rate limiting rule in the `sentinel-rule-cm`:
+
+```yaml
+domain: foo
+descriptors:
+ # For requests to the "service_httpbin" cluster, limit the max QPS to 1
+ - resources:
+ - key: "destination_cluster"
+ value: "service_httpbin"
+ count: 1
+```
+
+You may enable the access log in the Sentinel RLS token server (output to console)
+via the `SENTINEL_RLS_ACCESS_LOG` env:
+
+```yaml
+env:
+ - name: SENTINEL_RLS_ACCESS_LOG
+ value: "on"
+```
+
+You may also append JVM opts via the `JAVA_OPTS` env.
+
+After preparing the yaml template, you may deploy the Sentinel RLS token server:
+
+```bash
+kubectl apply -f sample/k8s/sentinel-rls.yml
+```
+
+## Deploy Envoy
+
+Next we could deploy the Envoy instances in the K8S cluster. If you've already had Envoy instances running,
+you could configure the address (`sentinel-rls-service`) and the port (`10245`)
+of the rate limit cluster in your Envoy configuration.
+
+We've provided a deployment template for Envoy in `envoy.yml`.
+It includes:
+
+- A `ConfigMap` that contains the configuration for Envoy.
+ This will be mounted as a file in the target `Deployment`, which will be loaded as the configuration
+ file by Envoy.
+- A `Deployment` for Envoy. It will mount the `ConfigMap` as a volume
+ for configuration.
+- A `Service` that exports the Envoy endpoint port (by default 10000) on a cluster-internal IP
+ so that it could be accessible from other pods. If you need external access, you could choose the
+ `LoadBalancer` type or add a frontend ingress.
+
+In the sample, we have two [Envoy clusters](https://www.envoyproxy.io/docs/envoy/latest/api-v2/clusters/clusters):
+
+- `service_httpbin`: HTTP proxy to `httpbin.org`
+- `rate_limit_cluster`: the cluster of the Sentinel RLS token server
+
+This route configuration tells Envoy to route incoming requests to `httpbin.org`. In the `http_filters` conf item,
+we added the `envoy.rate_limit` filter to the filter chain so that the global rate limiting is enabled.
+We set the rate limit domain as `foo`, which matches the item in the Envoy RLS rule.
+In the `route_config`, we provide the rate limit action: `{destination_cluster: {}}`, which will
+generate the rate limit descriptor containing the actual target cluster name (e.g. `service_httpbin`).
+Then we could set rate limit rules for each target clusters.
+
+After preparing the yaml template, you may deploy the Envoy instance:
+
+```bash
+kubectl apply -f sample/k8s/envoy.yml
+```
+
+## Test the rate limiting
+
+Now it's show time! We could visit the URL `envoy-service:10000/json` in K8S pods.
+Since we set the max QPS to 1, we could emit concurrent requests to the URL, and we
+could see the first request passes, while the latter requests are blocked (status 429):
+
+![image](https://user-images.githubusercontent.com/9434884/68571798-d0a46500-049e-11ea-8488-5e90f56f23a5.png)
+
+## Update the rules dynamically
+
+You could update the rules in the `sentinel-rule-cm` ConfigMap. Once the content is updated,
+Sentinel will perceive the changes and load the new rules to `EnvoyRlsRuleManager`.
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/envoy.yml b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/envoy.yml
new file mode 100644
index 0000000000..b732615804
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/envoy.yml
@@ -0,0 +1,132 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: envoy-cm
+data:
+ envoy-yml: |-
+ admin:
+ access_log_path: /tmp/admin_access.log
+ address:
+ socket_address:
+ protocol: TCP
+ address: 127.0.0.1
+ port_value: 9901
+ static_resources:
+ listeners:
+ - name: listener_0
+ address:
+ socket_address:
+ protocol: TCP
+ address: 0.0.0.0
+ port_value: 10000
+ filter_chains:
+ - filters:
+ - name: envoy.http_connection_manager
+ typed_config:
+ "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
+ stat_prefix: ingress_http
+ route_config:
+ name: local_route
+ virtual_hosts:
+ - name: local_service
+ domains: ["*"]
+ routes:
+ - match:
+ prefix: "/"
+ route:
+ host_rewrite: httpbin.org
+ cluster: service_httpbin
+ rate_limits:
+ - stage: 0
+ actions:
+ - {destination_cluster: {}}
+ http_filters:
+ - name: envoy.rate_limit
+ config:
+ domain: foo
+ stage: 0
+ rate_limit_service:
+ grpc_service:
+ envoy_grpc:
+ cluster_name: rate_limit_cluster
+ timeout: 0.25s
+ - name: envoy.router
+ clusters:
+ - name: service_httpbin
+ connect_timeout: 0.5s
+ type: LOGICAL_DNS
+ # Comment out the following line to test on v6 networks
+ dns_lookup_family: V4_ONLY
+ lb_policy: ROUND_ROBIN
+ load_assignment:
+ cluster_name: service_httpbin
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: httpbin.org
+ port_value: 80
+ - name: rate_limit_cluster
+ type: LOGICAL_DNS
+ connect_timeout: 0.25s
+ lb_policy: ROUND_ROBIN
+ http2_protocol_options: {}
+ load_assignment:
+ cluster_name: rate_limit_cluster
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: sentinel-rls-service
+ port_value: 10245
+---
+apiVersion: apps/v1beta2
+kind: Deployment
+metadata:
+ name: envoy-deployment-basic
+ labels:
+ app: envoy
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: envoy
+ template:
+ metadata:
+ labels:
+ app: envoy
+ spec:
+ containers:
+ - name: envoy
+ image: envoyproxy/envoy:v1.12.0
+ ports:
+ - containerPort: 10000
+ command: ["envoy"]
+ args: ["-c", "/tmp/envoy/envoy.yaml"]
+ volumeMounts:
+ - name: envoy-config
+ mountPath: /tmp/envoy
+ volumes:
+ - name: envoy-config
+ configMap:
+ name: envoy-cm
+ items:
+ - key: envoy-yml
+ path: envoy.yaml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: envoy-service
+ labels:
+ name: envoy-service
+spec:
+ type: ClusterIP
+ ports:
+ - port: 10000
+ targetPort: 10000
+ protocol: TCP
+ selector:
+ app: envoy
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/sentinel-rls.yml b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/sentinel-rls.yml
new file mode 100644
index 0000000000..1023932e37
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/sample/k8s/sentinel-rls.yml
@@ -0,0 +1,68 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: sentinel-rule-cm
+data:
+ rule-yaml: |-
+ domain: foo
+ descriptors:
+ - resources:
+ - key: "destination_cluster"
+ value: "service_httpbin"
+ count: 1
+---
+apiVersion: apps/v1beta2
+kind: Deployment
+metadata:
+ name: sentinel-rls-server
+ labels:
+ app: sentinel
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: sentinel
+ template:
+ metadata:
+ labels:
+ app: sentinel
+ spec:
+ containers:
+ - name: sentinelserver
+ # You could replace the image with your own image here
+ image: "registry.cn-hangzhou.aliyuncs.com/sentinel-docker-repo/sentinel-envoy-rls-server:latest"
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 10245
+ - containerPort: 8719
+ volumeMounts:
+ - name: sentinel-rule-config
+ mountPath: /tmp/sentinel
+ env:
+ - name: SENTINEL_RLS_RULE_FILE_PATH
+ value: "/tmp/sentinel/rule.yaml"
+ volumes:
+ - name: sentinel-rule-config
+ configMap:
+ name: sentinel-rule-cm
+ items:
+ - key: rule-yaml
+ path: rule.yaml
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: sentinel-rls-service
+ labels:
+ name: sentinel-rls-service
+spec:
+ type: ClusterIP
+ ports:
+ - port: 8719
+ targetPort: 8719
+ name: sentinel-command
+ - port: 10245
+ targetPort: 10245
+ name: sentinel-grpc
+ selector:
+ app: sentinel
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsConstants.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsConstants.java
new file mode 100644
index 0000000000..369fe3e05f
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsConstants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls;
+
+/**
+ * @author Eric Zhao
+ */
+public final class SentinelEnvoyRlsConstants {
+
+ public static final int DEFAULT_GRPC_PORT = 10245;
+ public static final String SERVER_APP_NAME = "sentinel-rls-token-server";
+
+ public static final String GRPC_PORT_ENV_KEY = "SENTINEL_RLS_GRPC_PORT";
+ public static final String GRPC_PORT_PROPERTY_KEY = "csp.sentinel.grpc.server.port";
+ public static final String RULE_FILE_PATH_ENV_KEY = "SENTINEL_RLS_RULE_FILE_PATH";
+ public static final String RULE_FILE_PATH_PROPERTY_KEY = "csp.sentinel.rls.rule.file";
+
+ public static final String ENABLE_ACCESS_LOG_ENV_KEY = "SENTINEL_RLS_ACCESS_LOG";
+
+ private SentinelEnvoyRlsConstants() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServer.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServer.java
new file mode 100644
index 0000000000..102add2d89
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls;
+
+import java.util.Optional;
+
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.datasource.EnvoyRlsRuleDataSourceService;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.init.InitExecutor;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ */
+public class SentinelEnvoyRlsServer {
+
+ public static void main(String[] args) throws Exception {
+ System.setProperty("project.name", SentinelEnvoyRlsConstants.SERVER_APP_NAME);
+
+ EnvoyRlsRuleDataSourceService dataSourceService = new EnvoyRlsRuleDataSourceService();
+ dataSourceService.init();
+
+ int port = resolvePort();
+ SentinelRlsGrpcServer server = new SentinelRlsGrpcServer(port);
+ server.start();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ System.err.println("[SentinelEnvoyRlsServer] Shutting down gRPC RLS server since JVM is shutting down");
+ server.shutdown();
+ dataSourceService.onShutdown();
+ System.err.println("[SentinelEnvoyRlsServer] Server has been shut down");
+ }));
+ InitExecutor.doInit();
+
+ server.blockUntilShutdown();
+ }
+
+ private static int resolvePort() {
+ final int defaultPort = SentinelEnvoyRlsConstants.DEFAULT_GRPC_PORT;
+ // Order: system env > property
+ String portStr = Optional.ofNullable(System.getenv(SentinelEnvoyRlsConstants.GRPC_PORT_ENV_KEY))
+ .orElse(SentinelConfig.getConfig(SentinelEnvoyRlsConstants.GRPC_PORT_PROPERTY_KEY));
+ if (StringUtil.isBlank(portStr)) {
+ return defaultPort;
+ }
+ try {
+ int port = Integer.parseInt(portStr);
+ if (port <= 0 || port > 65535) {
+ RecordLog.warn("[SentinelEnvoyRlsServer] Invalid port <" + portStr + ">, using default" + defaultPort);
+ return defaultPort;
+ }
+ return port;
+ } catch (Exception ex) {
+ RecordLog.warn("[SentinelEnvoyRlsServer] Failed to resolve port, using default " + defaultPort);
+ System.err.println("[SentinelEnvoyRlsServer] Failed to resolve port, using default " + defaultPort);
+ return defaultPort;
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImpl.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImpl.java
new file mode 100644
index 0000000000..7e0bc7b95b
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImpl.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow.SimpleClusterFlowChecker;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.log.RlsAccessLogger;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.util.function.Tuple2;
+
+import com.google.protobuf.TextFormat;
+import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor;
+import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor.Entry;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitRequest;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.Code;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.DescriptorStatus;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.RateLimit;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.RateLimit.Unit;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitServiceGrpc;
+import io.grpc.stub.StreamObserver;
+
+import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public class SentinelEnvoyRlsServiceImpl extends RateLimitServiceGrpc.RateLimitServiceImplBase {
+
+ @Override
+ public void shouldRateLimit(RateLimitRequest request, StreamObserver responseObserver) {
+ int acquireCount = request.getHitsAddend();
+ if (acquireCount < 0) {
+ responseObserver.onError(new IllegalArgumentException(
+ "acquireCount should be positive, but actual: " + acquireCount));
+ return;
+ }
+ if (acquireCount == 0) {
+ // Not present, use the default "1" by default.
+ acquireCount = 1;
+ }
+
+ String domain = request.getDomain();
+ boolean blocked = false;
+ List statusList = new ArrayList<>(request.getDescriptorsCount());
+ for (RateLimitDescriptor descriptor : request.getDescriptorsList()) {
+ Tuple2 t = checkToken(domain, descriptor, acquireCount);
+ TokenResult r = t.r2;
+
+ printAccessLogIfNecessary(domain, descriptor, r);
+
+ if (r.getStatus() == TokenResultStatus.NO_RULE_EXISTS) {
+ // If the rule of the descriptor is absent, the request will pass directly.
+ r.setStatus(TokenResultStatus.OK);
+ }
+
+ if (!blocked && r.getStatus() != TokenResultStatus.OK) {
+ blocked = true;
+ }
+
+ Code statusCode = r.getStatus() == TokenResultStatus.OK ? Code.OK : Code.OVER_LIMIT;
+ DescriptorStatus.Builder descriptorStatusBuilder = DescriptorStatus.newBuilder()
+ .setCode(statusCode);
+ if (t.r1 != null) {
+ descriptorStatusBuilder
+ .setCurrentLimit(RateLimit.newBuilder().setUnit(Unit.SECOND)
+ .setRequestsPerUnit((int)t.r1.getCount())
+ .build())
+ .setLimitRemaining(r.getRemaining());
+ }
+ statusList.add(descriptorStatusBuilder.build());
+ }
+
+ Code overallStatus = blocked ? Code.OVER_LIMIT : Code.OK;
+ RateLimitResponse response = RateLimitResponse.newBuilder()
+ .setOverallCode(overallStatus)
+ .addAllStatuses(statusList)
+ .build();
+
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ }
+
+ private void printAccessLogIfNecessary(String domain, RateLimitDescriptor descriptor, TokenResult result) {
+ if (!RlsAccessLogger.isEnabled()) {
+ return;
+ }
+ String message = new StringBuilder("[RlsAccessLog] domain=").append(domain)
+ .append(", descriptor=").append(TextFormat.shortDebugString(descriptor))
+ .append(", checkStatus=").append(result.getStatus())
+ .append(", remaining=").append(result.getRemaining())
+ .toString();
+ RlsAccessLogger.log(message);
+ }
+
+ protected Tuple2 checkToken(String domain, RateLimitDescriptor descriptor, int acquireCount) {
+ long ruleId = EnvoySentinelRuleConverter.generateFlowId(generateKey(domain, descriptor));
+
+ FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
+ if (rule == null) {
+ // Pass if the target rule is absent.
+ return Tuple2.of(null, new TokenResult(TokenResultStatus.NO_RULE_EXISTS));
+ }
+ // If the rule is present, it should be valid.
+ return Tuple2.of(rule, SimpleClusterFlowChecker.acquireClusterToken(rule, acquireCount));
+ }
+
+ private String generateKey(String domain, RateLimitDescriptor descriptor) {
+ StringBuilder sb = new StringBuilder(domain);
+ for (Entry resource : descriptor.getEntriesList()) {
+ sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue());
+ }
+ return sb.toString();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelRlsGrpcServer.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelRlsGrpcServer.java
new file mode 100644
index 0000000000..459540ad69
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelRlsGrpcServer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls;
+
+import java.io.IOException;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+
+/**
+ * @author Eric Zhao
+ */
+public class SentinelRlsGrpcServer {
+
+ private final Server server;
+
+ public SentinelRlsGrpcServer(int port) {
+ ServerBuilder> builder = ServerBuilder.forPort(port)
+ .addService(new SentinelEnvoyRlsServiceImpl());
+ server = builder.build();
+ }
+
+ public void start() throws IOException {
+ // The gRPC server has already checked the start status, so we don't check here.
+ server.start();
+ String message = "[SentinelRlsGrpcServer] RLS server is running at port " + server.getPort();
+ RecordLog.info(message);
+ System.out.println(message);
+ }
+
+ public void shutdown() {
+ server.shutdownNow();
+ }
+
+ public boolean isShutdown() {
+ return server.isShutdown();
+ }
+
+ public void blockUntilShutdown() throws InterruptedException {
+ if (server != null) {
+ server.awaitTermination();
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/datasource/EnvoyRlsRuleDataSourceService.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/datasource/EnvoyRlsRuleDataSourceService.java
new file mode 100644
index 0000000000..4cf0f07e72
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/datasource/EnvoyRlsRuleDataSourceService.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.datasource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsConstants;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRuleManager;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource;
+import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.representer.Representer;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public class EnvoyRlsRuleDataSourceService {
+
+ private final Yaml yaml;
+ private ReadableDataSource> ds;
+
+ public EnvoyRlsRuleDataSourceService() {
+ this.yaml = createYamlParser();
+ }
+
+ private Yaml createYamlParser() {
+ Representer representer = new Representer();
+ representer.getPropertyUtils().setSkipMissingProperties(true);
+ return new Yaml(representer);
+ }
+
+ public synchronized void init() throws Exception {
+ if (ds != null) {
+ return;
+ }
+ String configPath = getRuleConfigPath();
+ if (StringUtil.isBlank(configPath)) {
+ throw new IllegalStateException("Empty rule config path, please set the file path in the env: "
+ + SentinelEnvoyRlsConstants.RULE_FILE_PATH_ENV_KEY);
+ }
+
+ this.ds = new FileRefreshableDataSource<>(configPath, s -> Arrays.asList(yaml.loadAs(s, EnvoyRlsRule.class)));
+ EnvoyRlsRuleManager.register2Property(ds.getProperty());
+ }
+
+ public synchronized void onShutdown() {
+ if (ds != null) {
+ try {
+ ds.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private String getRuleConfigPath() {
+ return Optional.ofNullable(System.getenv(SentinelEnvoyRlsConstants.RULE_FILE_PATH_ENV_KEY))
+ .orElse(SentinelConfig.getConfig(SentinelEnvoyRlsConstants.RULE_FILE_PATH_PROPERTY_KEY));
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/flow/SimpleClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/flow/SimpleClusterFlowChecker.java
new file mode 100644
index 0000000000..efa307cd34
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/flow/SimpleClusterFlowChecker.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow;
+
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
+import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
+import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public final class SimpleClusterFlowChecker {
+
+ public static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount) {
+ Long id = rule.getClusterConfig().getFlowId();
+
+ ClusterMetric metric = ClusterMetricStatistics.getMetric(id);
+ if (metric == null) {
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+
+ double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
+ double globalThreshold = rule.getCount() * ClusterServerConfigManager.getExceedCount();
+ double nextRemaining = globalThreshold - latestQps - acquireCount;
+
+ if (nextRemaining >= 0) {
+ metric.add(ClusterFlowEvent.PASS, acquireCount);
+ metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
+
+ ClusterServerStatLogUtil.log("flow|pass|" + id, acquireCount);
+ ClusterServerStatLogUtil.log("flow|pass_request|" + id, 1);
+
+ // Remaining count is cut down to a smaller integer.
+ return new TokenResult(TokenResultStatus.OK)
+ .setRemaining((int) nextRemaining)
+ .setWaitInMs(0);
+ } else {
+ // Blocked.
+ metric.add(ClusterFlowEvent.BLOCK, acquireCount);
+ metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1);
+ ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount);
+ ClusterServerStatLogUtil.log("flow|block_request|" + id, 1);
+
+ return blockedResult();
+ }
+ }
+
+ private static TokenResult blockedResult() {
+ return new TokenResult(TokenResultStatus.BLOCKED)
+ .setRemaining(0)
+ .setWaitInMs(0);
+ }
+
+ private SimpleClusterFlowChecker() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/log/RlsAccessLogger.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/log/RlsAccessLogger.java
new file mode 100644
index 0000000000..7dc23b46f5
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/log/RlsAccessLogger.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.log;
+
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.SentinelEnvoyRlsConstants;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ */
+public final class RlsAccessLogger {
+
+ private static boolean enabled = false;
+
+ static {
+ try {
+ enabled = "on".equalsIgnoreCase(System.getenv(SentinelEnvoyRlsConstants.ENABLE_ACCESS_LOG_ENV_KEY));
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public static boolean isEnabled() {
+ return enabled;
+ }
+
+ public static void log(String info) {
+ if (enabled && StringUtil.isNotEmpty(info)) {
+ System.out.println(info);
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRule.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRule.java
new file mode 100644
index 0000000000..fe66d4fd98
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRule.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public class EnvoyRlsRule {
+
+ private String domain;
+ private List descriptors;
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public void setDomain(String domain) {
+ this.domain = domain;
+ }
+
+ public List getDescriptors() {
+ return descriptors;
+ }
+
+ public void setDescriptors(List descriptors) {
+ this.descriptors = descriptors;
+ }
+
+ @Override
+ public String toString() {
+ return "EnvoyRlsRule{" +
+ "domain='" + domain + '\'' +
+ ", descriptors=" + descriptors +
+ '}';
+ }
+
+ public static class ResourceDescriptor {
+
+ private Set resources;
+
+ private Double count;
+
+ public ResourceDescriptor() {}
+
+ public ResourceDescriptor(Set resources, Double count) {
+ this.resources = resources;
+ this.count = count;
+ }
+
+ public Set getResources() {
+ return resources;
+ }
+
+ public void setResources(Set resources) {
+ this.resources = resources;
+ }
+
+ public Double getCount() {
+ return count;
+ }
+
+ public void setCount(Double count) {
+ this.count = count;
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceDescriptor{" +
+ "resources=" + resources +
+ ", count=" + count +
+ '}';
+ }
+ }
+
+ public static class KeyValueResource {
+
+ private String key;
+ private String value;
+
+ public KeyValueResource() {}
+
+ public KeyValueResource(String key, String value) {
+ AssertUtil.assertNotBlank(key, "key cannot be blank");
+ AssertUtil.assertNotBlank(value, "value cannot be blank");
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+ KeyValueResource that = (KeyValueResource)o;
+ return Objects.equals(key, that.key) &&
+ Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, value);
+ }
+
+ @Override
+ public String toString() {
+ return "KeyValueResource{" +
+ "key='" + key + '\'' +
+ ", value='" + value + '\'' +
+ '}';
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java
new file mode 100644
index 0000000000..1acca60040
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
+import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.alibaba.csp.sentinel.property.PropertyListener;
+import com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.alibaba.csp.sentinel.property.SimplePropertyListener;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public final class EnvoyRlsRuleManager {
+
+ private static final ConcurrentMap RULE_MAP = new ConcurrentHashMap<>();
+
+ private static final PropertyListener> PROPERTY_LISTENER = new EnvoyRlsRulePropertyListener();
+ private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>();
+
+ static {
+ currentProperty.addListener(PROPERTY_LISTENER);
+ }
+
+ /**
+ * Listen to the {@link SentinelProperty} for Envoy RLS rules. The property is the source of {@link EnvoyRlsRule}.
+ *
+ * @param property the property to listen
+ */
+ public static void register2Property(SentinelProperty> property) {
+ AssertUtil.notNull(property, "property cannot be null");
+ synchronized (PROPERTY_LISTENER) {
+ RecordLog.info("[EnvoyRlsRuleManager] Registering new property to Envoy rate limit service rule manager");
+ currentProperty.removeListener(PROPERTY_LISTENER);
+ property.addListener(PROPERTY_LISTENER);
+ currentProperty = property;
+ }
+ }
+
+ /**
+ * Load Envoy RLS rules, while former rules will be replaced.
+ *
+ * @param rules new rules to load
+ * @return true if there are actual changes, otherwise false
+ */
+ public static boolean loadRules(List rules) {
+ return currentProperty.updateValue(rules);
+ }
+
+ public static List getRules() {
+ return new ArrayList<>(RULE_MAP.values());
+ }
+
+ static final class EnvoyRlsRulePropertyListener extends SimplePropertyListener> {
+
+ @Override
+ public synchronized void configUpdate(List conf) {
+ Map ruleMap = generateRuleMap(conf);
+
+ List flowRules = ruleMap.values().stream()
+ .flatMap(e -> EnvoySentinelRuleConverter.toSentinelFlowRules(e).stream())
+ .collect(Collectors.toList());
+
+ RULE_MAP.clear();
+ RULE_MAP.putAll(ruleMap);
+ RecordLog.info("[EnvoyRlsRuleManager] Envoy RLS rules loaded: " + flowRules);
+
+ // Use the "default" namespace.
+ ClusterFlowRuleManager.loadRules(ServerConstants.DEFAULT_NAMESPACE, flowRules);
+ }
+
+ Map generateRuleMap(List conf) {
+ if (conf == null || conf.isEmpty()) {
+ return new HashMap<>(2);
+ }
+ Map map = new HashMap<>(conf.size());
+ for (EnvoyRlsRule rule : conf) {
+ if (!isValidRule(rule)) {
+ RecordLog.warn("[EnvoyRlsRuleManager] Ignoring invalid rule when loading new RLS rules: " + rule);
+ continue;
+ }
+ if (map.containsKey(rule.getDomain())) {
+ RecordLog.warn("[EnvoyRlsRuleManager] Ignoring duplicate RLS rule for specific domain: " + rule);
+ continue;
+ }
+ map.put(rule.getDomain(), rule);
+ }
+ return map;
+ }
+ }
+
+ /**
+ * Check whether the given Envoy RLS rule is valid.
+ *
+ * @param rule the rule to check
+ * @return true if the rule is valid, otherwise false
+ */
+ public static boolean isValidRule(EnvoyRlsRule rule) {
+ if (rule == null || StringUtil.isBlank(rule.getDomain())) {
+ return false;
+ }
+ List descriptors = rule.getDescriptors();
+ if (descriptors == null || descriptors.isEmpty()) {
+ return false;
+ }
+ for (EnvoyRlsRule.ResourceDescriptor descriptor : descriptors) {
+ if (descriptor == null || descriptor.getCount() == null || descriptor.getCount() < 0) {
+ return false;
+ }
+ Set resources = descriptor.getResources();
+ if (resources == null || resources.isEmpty()) {
+ return false;
+ }
+ for (EnvoyRlsRule.KeyValueResource resource : resources) {
+ if (resource == null ||
+ StringUtil.isBlank(resource.getKey()) || StringUtil.isBlank(resource.getValue())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private EnvoyRlsRuleManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverter.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverter.java
new file mode 100644
index 0000000000..8ae33c2cde
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public final class EnvoySentinelRuleConverter {
+
+ /**
+ * Currently we use "|" to separate each key/value entries.
+ */
+ public static final String SEPARATOR = "|";
+
+ /**
+ * Convert the {@link EnvoyRlsRule} to a list of Sentinel flow rules.
+ *
+ * @param rule a valid Envoy RLS rule
+ * @return converted rules
+ */
+ public static List toSentinelFlowRules(EnvoyRlsRule rule) {
+ if (!EnvoyRlsRuleManager.isValidRule(rule)) {
+ throw new IllegalArgumentException("Not a valid RLS rule");
+ }
+ return rule.getDescriptors().stream()
+ .map(e -> toSentinelFlowRule(rule.getDomain(), e))
+ .collect(Collectors.toList());
+ }
+
+ public static FlowRule toSentinelFlowRule(String domain, EnvoyRlsRule.ResourceDescriptor descriptor) {
+ // One descriptor could have only one rule.
+ String identifier = generateKey(domain, descriptor);
+ long flowId = generateFlowId(identifier);
+ return new FlowRule(identifier)
+ .setCount(descriptor.getCount())
+ .setClusterMode(true)
+ .setClusterConfig(new ClusterFlowConfig()
+ .setFlowId(flowId)
+ .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL)
+ .setSampleCount(1)
+ .setFallbackToLocalWhenFail(false));
+ }
+
+ public static long generateFlowId(String key) {
+ if (StringUtil.isBlank(key)) {
+ return -1L;
+ }
+ // Add offset to avoid negative ID.
+ return (long) Integer.MAX_VALUE + key.hashCode();
+ }
+
+ public static String generateKey(String domain, EnvoyRlsRule.ResourceDescriptor descriptor) {
+ AssertUtil.assertNotBlank(domain, "domain cannot be blank");
+ AssertUtil.notNull(descriptor, "EnvoyRlsRule.ResourceDescriptor cannot be null");
+ AssertUtil.assertNotEmpty(descriptor.getResources(), "resources in descriptor cannot be null");
+
+ StringBuilder sb = new StringBuilder(domain);
+ for (EnvoyRlsRule.KeyValueResource resource : descriptor.getResources()) {
+ sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue());
+ }
+ return sb.toString();
+ }
+
+ private EnvoySentinelRuleConverter() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/core/base.proto b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/core/base.proto
new file mode 100644
index 0000000000..4903df1858
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/core/base.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+
+package envoy.api.v2.core;
+
+option java_outer_classname = "BaseProto";
+option java_multiple_files = true;
+option java_package = "io.envoyproxy.envoy.api.v2.core";
+
+import "google/protobuf/any.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/wrappers.proto";
+
+import "validate/validate.proto";
+
+// Header name/value pair.
+message HeaderValue {
+ // Header name.
+ string key = 1 [(validate.rules).string = {min_bytes: 1 max_bytes: 16384}];
+
+ // Header value.
+ //
+ // The same :ref:`format specifier ` as used for
+ // :ref:`HTTP access logging ` applies here, however
+ // unknown header values are replaced with the empty string instead of `-`.
+ string value = 2 [(validate.rules).string = {max_bytes: 16384}];
+}
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/ratelimit/ratelimit.proto b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/ratelimit/ratelimit.proto
new file mode 100644
index 0000000000..dfcd4174eb
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/api/v2/ratelimit/ratelimit.proto
@@ -0,0 +1,65 @@
+syntax = "proto3";
+
+package envoy.api.v2.ratelimit;
+
+option java_outer_classname = "RatelimitProto";
+option java_multiple_files = true;
+option java_package = "io.envoyproxy.envoy.api.v2.ratelimit";
+
+import "validate/validate.proto";
+
+// [#protodoc-title: Common rate limit components]
+
+// A RateLimitDescriptor is a list of hierarchical entries that are used by the service to
+// determine the final rate limit key and overall allowed limit. Here are some examples of how
+// they might be used for the domain "envoy".
+//
+// .. code-block:: cpp
+//
+// ["authenticated": "false"], ["remote_address": "10.0.0.1"]
+//
+// What it does: Limits all unauthenticated traffic for the IP address 10.0.0.1. The
+// configuration supplies a default limit for the *remote_address* key. If there is a desire to
+// raise the limit for 10.0.0.1 or block it entirely it can be specified directly in the
+// configuration.
+//
+// .. code-block:: cpp
+//
+// ["authenticated": "false"], ["path": "/foo/bar"]
+//
+// What it does: Limits all unauthenticated traffic globally for a specific path (or prefix if
+// configured that way in the service).
+//
+// .. code-block:: cpp
+//
+// ["authenticated": "false"], ["path": "/foo/bar"], ["remote_address": "10.0.0.1"]
+//
+// What it does: Limits unauthenticated traffic to a specific path for a specific IP address.
+// Like (1) we can raise/block specific IP addresses if we want with an override configuration.
+//
+// .. code-block:: cpp
+//
+// ["authenticated": "true"], ["client_id": "foo"]
+//
+// What it does: Limits all traffic for an authenticated client "foo"
+//
+// .. code-block:: cpp
+//
+// ["authenticated": "true"], ["client_id": "foo"], ["path": "/foo/bar"]
+//
+// What it does: Limits traffic to a specific path for an authenticated client "foo"
+//
+// The idea behind the API is that (1)/(2)/(3) and (4)/(5) can be sent in 1 request if desired.
+// This enables building complex application scenarios with a generic backend.
+message RateLimitDescriptor {
+ message Entry {
+ // Descriptor key.
+ string key = 1 [(validate.rules).string = {min_bytes: 1}];
+
+ // Descriptor value.
+ string value = 2 [(validate.rules).string = {min_bytes: 1}];
+ }
+
+ // Descriptor entries.
+ repeated Entry entries = 1 [(validate.rules).repeated = {min_items: 1}];
+}
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/service/ratelimit/v2/rls.proto b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/service/ratelimit/v2/rls.proto
new file mode 100644
index 0000000000..a737a51e07
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/envoy/service/ratelimit/v2/rls.proto
@@ -0,0 +1,109 @@
+syntax = "proto3";
+
+package envoy.service.ratelimit.v2;
+
+option java_outer_classname = "RlsProto";
+option java_multiple_files = true;
+option java_package = "io.envoyproxy.envoy.service.ratelimit.v2";
+option java_generic_services = true;
+
+import "envoy/api/v2/core/base.proto";
+import "envoy/api/v2/ratelimit/ratelimit.proto";
+
+import "validate/validate.proto";
+
+// [#protodoc-title: Rate Limit Service (RLS)]
+
+service RateLimitService {
+ // Determine whether rate limiting should take place.
+ rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) {
+ }
+}
+
+// Main message for a rate limit request. The rate limit service is designed to be fully generic
+// in the sense that it can operate on arbitrary hierarchical key/value pairs. The loaded
+// configuration will parse the request and find the most specific limit to apply. In addition,
+// a RateLimitRequest can contain multiple "descriptors" to limit on. When multiple descriptors
+// are provided, the server will limit on *ALL* of them and return an OVER_LIMIT response if any
+// of them are over limit. This enables more complex application level rate limiting scenarios
+// if desired.
+message RateLimitRequest {
+ // All rate limit requests must specify a domain. This enables the configuration to be per
+ // application without fear of overlap. E.g., "envoy".
+ string domain = 1;
+
+ // All rate limit requests must specify at least one RateLimitDescriptor. Each descriptor is
+ // processed by the service (see below). If any of the descriptors are over limit, the entire
+ // request is considered to be over limit.
+ repeated api.v2.ratelimit.RateLimitDescriptor descriptors = 2;
+
+ // Rate limit requests can optionally specify the number of hits a request adds to the matched
+ // limit. If the value is not set in the message, a request increases the matched limit by 1.
+ uint32 hits_addend = 3;
+}
+
+// A response from a ShouldRateLimit call.
+message RateLimitResponse {
+ enum Code {
+ // The response code is not known.
+ UNKNOWN = 0;
+
+ // The response code to notify that the number of requests are under limit.
+ OK = 1;
+
+ // The response code to notify that the number of requests are over limit.
+ OVER_LIMIT = 2;
+ }
+
+ // Defines an actual rate limit in terms of requests per unit of time and the unit itself.
+ message RateLimit {
+ enum Unit {
+ // The time unit is not known.
+ UNKNOWN = 0;
+
+ // The time unit representing a second.
+ SECOND = 1;
+
+ // The time unit representing a minute.
+ MINUTE = 2;
+
+ // The time unit representing an hour.
+ HOUR = 3;
+
+ // The time unit representing a day.
+ DAY = 4;
+ }
+
+ // The number of requests per unit of time.
+ uint32 requests_per_unit = 1;
+
+ // The unit of time.
+ Unit unit = 2;
+ }
+
+ message DescriptorStatus {
+ // The response code for an individual descriptor.
+ Code code = 1;
+
+ // The current limit as configured by the server. Useful for debugging, etc.
+ RateLimit current_limit = 2;
+
+ // The limit remaining in the current time unit.
+ uint32 limit_remaining = 3;
+ }
+
+ // The overall response code which takes into account all of the descriptors that were passed
+ // in the RateLimitRequest message.
+ Code overall_code = 1;
+
+ // A list of DescriptorStatus messages which matches the length of the descriptor list passed
+ // in the RateLimitRequest. This can be used by the caller to determine which individual
+ // descriptors failed and/or what the currently configured limits are for all of them.
+ repeated DescriptorStatus statuses = 2;
+
+ // [#next-major-version: rename to response_headers_to_add]
+ repeated api.v2.core.HeaderValue headers = 3;
+
+ // A list of headers to add to the request when forwarded
+ repeated api.v2.core.HeaderValue request_headers_to_add = 4;
+}
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/validate/validate.proto b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/validate/validate.proto
new file mode 100644
index 0000000000..b662551352
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/proto/validate/validate.proto
@@ -0,0 +1,763 @@
+syntax = "proto2";
+package validate;
+
+option go_package = "github.com/lyft/protoc-gen-validate/validate";
+option java_package = "com.lyft.pgv.validate";
+
+import "google/protobuf/descriptor.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/timestamp.proto";
+
+// Validation rules applied at the message level
+extend google.protobuf.MessageOptions {
+ // Disabled nullifies any validation rules for this message, including any
+ // message fields associated with it that do support validation.
+ optional bool disabled = 919191;
+}
+
+// Validation rules applied at the oneof level
+extend google.protobuf.OneofOptions {
+ // Required ensures that exactly one the field options in a oneof is set;
+ // validation fails if no fields in the oneof are set.
+ optional bool required = 919191;
+}
+
+// Validation rules applied at the field level
+extend google.protobuf.FieldOptions {
+ // Rules specify the validations to be performed on this field. By default,
+ // no validation is performed against a field.
+ optional FieldRules rules = 919191;
+}
+
+// FieldRules encapsulates the rules for each type of field. Depending on the
+// field, the correct set should be used to ensure proper validations.
+message FieldRules {
+ oneof type {
+ // Scalar Field Types
+ FloatRules float = 1;
+ DoubleRules double = 2;
+ Int32Rules int32 = 3;
+ Int64Rules int64 = 4;
+ UInt32Rules uint32 = 5;
+ UInt64Rules uint64 = 6;
+ SInt32Rules sint32 = 7;
+ SInt64Rules sint64 = 8;
+ Fixed32Rules fixed32 = 9;
+ Fixed64Rules fixed64 = 10;
+ SFixed32Rules sfixed32 = 11;
+ SFixed64Rules sfixed64 = 12;
+ BoolRules bool = 13;
+ StringRules string = 14;
+ BytesRules bytes = 15;
+
+ // Complex Field Types
+ EnumRules enum = 16;
+ MessageRules message = 17;
+ RepeatedRules repeated = 18;
+ MapRules map = 19;
+
+ // Well-Known Field Types
+ AnyRules any = 20;
+ DurationRules duration = 21;
+ TimestampRules timestamp = 22;
+ }
+}
+
+// FloatRules describes the constraints applied to `float` values
+message FloatRules {
+ // Const specifies that this field must be exactly the specified value
+ optional float const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional float lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional float lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional float gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional float gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated float in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated float not_in = 7;
+}
+
+// DoubleRules describes the constraints applied to `double` values
+message DoubleRules {
+ // Const specifies that this field must be exactly the specified value
+ optional double const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional double lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional double lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional double gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional double gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated double in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated double not_in = 7;
+}
+
+// Int32Rules describes the constraints applied to `int32` values
+message Int32Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional int32 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional int32 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional int32 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional int32 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional int32 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated int32 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated int32 not_in = 7;
+}
+
+// Int64Rules describes the constraints applied to `int64` values
+message Int64Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional int64 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional int64 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional int64 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional int64 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional int64 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated int64 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated int64 not_in = 7;
+}
+
+// UInt32Rules describes the constraints applied to `uint32` values
+message UInt32Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional uint32 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional uint32 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional uint32 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional uint32 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional uint32 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated uint32 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated uint32 not_in = 7;
+}
+
+// UInt64Rules describes the constraints applied to `uint64` values
+message UInt64Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional uint64 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional uint64 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional uint64 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional uint64 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional uint64 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated uint64 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated uint64 not_in = 7;
+}
+
+// SInt32Rules describes the constraints applied to `sint32` values
+message SInt32Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional sint32 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional sint32 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional sint32 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional sint32 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional sint32 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated sint32 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated sint32 not_in = 7;
+}
+
+// SInt64Rules describes the constraints applied to `sint64` values
+message SInt64Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional sint64 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional sint64 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional sint64 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional sint64 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional sint64 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated sint64 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated sint64 not_in = 7;
+}
+
+// Fixed32Rules describes the constraints applied to `fixed32` values
+message Fixed32Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional fixed32 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional fixed32 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional fixed32 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional fixed32 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional fixed32 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated fixed32 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated fixed32 not_in = 7;
+}
+
+// Fixed64Rules describes the constraints applied to `fixed64` values
+message Fixed64Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional fixed64 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional fixed64 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional fixed64 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional fixed64 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional fixed64 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated fixed64 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated fixed64 not_in = 7;
+}
+
+// SFixed32Rules describes the constraints applied to `sfixed32` values
+message SFixed32Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional sfixed32 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional sfixed32 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional sfixed32 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional sfixed32 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional sfixed32 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated sfixed32 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated sfixed32 not_in = 7;
+}
+
+// SFixed64Rules describes the constraints applied to `sfixed64` values
+message SFixed64Rules {
+ // Const specifies that this field must be exactly the specified value
+ optional sfixed64 const = 1;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional sfixed64 lt = 2;
+
+ // Lte specifies that this field must be less than or equal to the
+ // specified value, inclusive
+ optional sfixed64 lte = 3;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive. If the value of Gt is larger than a specified Lt or Lte, the
+ // range is reversed.
+ optional sfixed64 gt = 4;
+
+ // Gte specifies that this field must be greater than or equal to the
+ // specified value, inclusive. If the value of Gte is larger than a
+ // specified Lt or Lte, the range is reversed.
+ optional sfixed64 gte = 5;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated sfixed64 in = 6;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated sfixed64 not_in = 7;
+}
+
+// BoolRules describes the constraints applied to `bool` values
+message BoolRules {
+ // Const specifies that this field must be exactly the specified value
+ optional bool const = 1;
+}
+
+// StringRules describe the constraints applied to `string` values
+message StringRules {
+ // Const specifies that this field must be exactly the specified value
+ optional string const = 1;
+
+ // Len specifies that this field must be the specified number of
+ // characters (Unicode code points). Note that the number of
+ // characters may differ from the number of bytes in the string.
+ optional uint64 len = 19;
+
+ // MinLen specifies that this field must be the specified number of
+ // characters (Unicode code points) at a minimum. Note that the number of
+ // characters may differ from the number of bytes in the string.
+ optional uint64 min_len = 2;
+
+ // MaxLen specifies that this field must be the specified number of
+ // characters (Unicode code points) at a maximum. Note that the number of
+ // characters may differ from the number of bytes in the string.
+ optional uint64 max_len = 3;
+
+ // LenBytes specifies that this field must be the specified number of bytes
+ // at a minimum
+ optional uint64 len_bytes = 20;
+
+ // MinBytes specifies that this field must be the specified number of bytes
+ // at a minimum
+ optional uint64 min_bytes = 4;
+
+ // MaxBytes specifies that this field must be the specified number of bytes
+ // at a maximum
+ optional uint64 max_bytes = 5;
+
+ // Pattern specifes that this field must match against the specified
+ // regular expression (RE2 syntax). The included expression should elide
+ // any delimiters.
+ optional string pattern = 6;
+
+ // Prefix specifies that this field must have the specified substring at
+ // the beginning of the string.
+ optional string prefix = 7;
+
+ // Suffix specifies that this field must have the specified substring at
+ // the end of the string.
+ optional string suffix = 8;
+
+ // Contains specifies that this field must have the specified substring
+ // anywhere in the string.
+ optional string contains = 9;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated string in = 10;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated string not_in = 11;
+
+ // WellKnown rules provide advanced constraints against common string
+ // patterns
+ oneof well_known {
+ // Email specifies that the field must be a valid email address as
+ // defined by RFC 5322
+ bool email = 12;
+
+ // Hostname specifies that the field must be a valid hostname as
+ // defined by RFC 1034. This constraint does not support
+ // internationalized domain names (IDNs).
+ bool hostname = 13;
+
+ // Ip specifies that the field must be a valid IP (v4 or v6) address.
+ // Valid IPv6 addresses should not include surrounding square brackets.
+ bool ip = 14;
+
+ // Ipv4 specifies that the field must be a valid IPv4 address.
+ bool ipv4 = 15;
+
+ // Ipv6 specifies that the field must be a valid IPv6 address. Valid
+ // IPv6 addresses should not include surrounding square brackets.
+ bool ipv6 = 16;
+
+ // Uri specifies that the field must be a valid, absolute URI as defined
+ // by RFC 3986
+ bool uri = 17;
+
+ // UriRef specifies that the field must be a valid URI as defined by RFC
+ // 3986 and may be relative or absolute.
+ bool uri_ref = 18;
+ }
+}
+
+// BytesRules describe the constraints applied to `bytes` values
+message BytesRules {
+ // Const specifies that this field must be exactly the specified value
+ optional bytes const = 1;
+
+ // Len specifies that this field must be the specified number of bytes
+ optional uint64 len = 13;
+
+ // MinLen specifies that this field must be the specified number of bytes
+ // at a minimum
+ optional uint64 min_len = 2;
+
+ // MaxLen specifies that this field must be the specified number of bytes
+ // at a maximum
+ optional uint64 max_len = 3;
+
+ // Pattern specifes that this field must match against the specified
+ // regular expression (RE2 syntax). The included expression should elide
+ // any delimiters.
+ optional string pattern = 4;
+
+ // Prefix specifies that this field must have the specified bytes at the
+ // beginning of the string.
+ optional bytes prefix = 5;
+
+ // Suffix specifies that this field must have the specified bytes at the
+ // end of the string.
+ optional bytes suffix = 6;
+
+ // Contains specifies that this field must have the specified bytes
+ // anywhere in the string.
+ optional bytes contains = 7;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated bytes in = 8;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated bytes not_in = 9;
+
+ // WellKnown rules provide advanced constraints against common byte
+ // patterns
+ oneof well_known {
+ // Ip specifies that the field must be a valid IP (v4 or v6) address in
+ // byte format
+ bool ip = 10;
+
+ // Ipv4 specifies that the field must be a valid IPv4 address in byte
+ // format
+ bool ipv4 = 11;
+
+ // Ipv6 specifies that the field must be a valid IPv6 address in byte
+ // format
+ bool ipv6 = 12;
+ }
+}
+
+// EnumRules describe the constraints applied to enum values
+message EnumRules {
+ // Const specifies that this field must be exactly the specified value
+ optional int32 const = 1;
+
+ // DefinedOnly specifies that this field must be only one of the defined
+ // values for this enum, failing on any undefined value.
+ optional bool defined_only = 2;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated int32 in = 3;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated int32 not_in = 4;
+}
+
+// MessageRules describe the constraints applied to embedded message values.
+// For message-type fields, validation is performed recursively.
+message MessageRules {
+ // Skip specifies that the validation rules of this field should not be
+ // evaluated
+ optional bool skip = 1;
+
+ // Required specifies that this field must be set
+ optional bool required = 2;
+}
+
+// RepeatedRules describe the constraints applied to `repeated` values
+message RepeatedRules {
+ // MinItems specifies that this field must have the specified number of
+ // items at a minimum
+ optional uint64 min_items = 1;
+
+ // MaxItems specifies that this field must have the specified number of
+ // items at a maximum
+ optional uint64 max_items = 2;
+
+ // Unique specifies that all elements in this field must be unique. This
+ // contraint is only applicable to scalar and enum types (messages are not
+ // supported).
+ optional bool unique = 3;
+
+ // Items specifies the contraints to be applied to each item in the field.
+ // Repeated message fields will still execute validation against each item
+ // unless skip is specified here.
+ optional FieldRules items = 4;
+}
+
+// MapRules describe the constraints applied to `map` values
+message MapRules {
+ // MinPairs specifies that this field must have the specified number of
+ // KVs at a minimum
+ optional uint64 min_pairs = 1;
+
+ // MaxPairs specifies that this field must have the specified number of
+ // KVs at a maximum
+ optional uint64 max_pairs = 2;
+
+ // NoSparse specifies values in this field cannot be unset. This only
+ // applies to map's with message value types.
+ optional bool no_sparse = 3;
+
+ // Keys specifies the constraints to be applied to each key in the field.
+ optional FieldRules keys = 4;
+
+ // Values specifies the constraints to be applied to the value of each key
+ // in the field. Message values will still have their validations evaluated
+ // unless skip is specified here.
+ optional FieldRules values = 5;
+}
+
+// AnyRules describe constraints applied exclusively to the
+// `google.protobuf.Any` well-known type
+message AnyRules {
+ // Required specifies that this field must be set
+ optional bool required = 1;
+
+ // In specifies that this field's `type_url` must be equal to one of the
+ // specified values.
+ repeated string in = 2;
+
+ // NotIn specifies that this field's `type_url` must not be equal to any of
+ // the specified values.
+ repeated string not_in = 3;
+}
+
+// DurationRules describe the constraints applied exclusively to the
+// `google.protobuf.Duration` well-known type
+message DurationRules {
+ // Required specifies that this field must be set
+ optional bool required = 1;
+
+ // Const specifies that this field must be exactly the specified value
+ optional google.protobuf.Duration const = 2;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional google.protobuf.Duration lt = 3;
+
+ // Lt specifies that this field must be less than the specified value,
+ // inclusive
+ optional google.protobuf.Duration lte = 4;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive
+ optional google.protobuf.Duration gt = 5;
+
+ // Gte specifies that this field must be greater than the specified value,
+ // inclusive
+ optional google.protobuf.Duration gte = 6;
+
+ // In specifies that this field must be equal to one of the specified
+ // values
+ repeated google.protobuf.Duration in = 7;
+
+ // NotIn specifies that this field cannot be equal to one of the specified
+ // values
+ repeated google.protobuf.Duration not_in = 8;
+}
+
+// TimestampRules describe the constraints applied exclusively to the
+// `google.protobuf.Timestamp` well-known type
+message TimestampRules {
+ // Required specifies that this field must be set
+ optional bool required = 1;
+
+ // Const specifies that this field must be exactly the specified value
+ optional google.protobuf.Timestamp const = 2;
+
+ // Lt specifies that this field must be less than the specified value,
+ // exclusive
+ optional google.protobuf.Timestamp lt = 3;
+
+ // Lte specifies that this field must be less than the specified value,
+ // inclusive
+ optional google.protobuf.Timestamp lte = 4;
+
+ // Gt specifies that this field must be greater than the specified value,
+ // exclusive
+ optional google.protobuf.Timestamp gt = 5;
+
+ // Gte specifies that this field must be greater than the specified value,
+ // inclusive
+ optional google.protobuf.Timestamp gte = 6;
+
+ // LtNow specifies that this must be less than the current time. LtNow
+ // can only be used with the Within rule.
+ optional bool lt_now = 7;
+
+ // GtNow specifies that this must be greater than the current time. GtNow
+ // can only be used with the Within rule.
+ optional bool gt_now = 8;
+
+ // Within specifies that this field must be within this duration of the
+ // current time. This constraint can be used alone or with the LtNow and
+ // GtNow rules.
+ optional google.protobuf.Duration within = 9;
+}
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImplTest.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImplTest.java
new file mode 100644
index 0000000000..d72618961d
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/SentinelEnvoyRlsServiceImplTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls;
+
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.util.function.Tuple2;
+
+import io.envoyproxy.envoy.api.v2.ratelimit.RateLimitDescriptor;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitRequest;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse;
+import io.envoyproxy.envoy.service.ratelimit.v2.RateLimitResponse.Code;
+import io.grpc.stub.StreamObserver;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class SentinelEnvoyRlsServiceImplTest {
+
+ @Test
+ public void testShouldRateLimitPass() {
+ SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class);
+ StreamObserver streamObserver = mock(StreamObserver.class);
+ String domain = "testShouldRateLimitPass";
+ int acquireCount = 1;
+
+ RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder()
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a1").setValue("b1").build())
+ .build();
+ RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder()
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a2").setValue("b2").build())
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a3").setValue("b3").build())
+ .build();
+
+ ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class);
+ doNothing().when(streamObserver)
+ .onNext(responseCapture.capture());
+
+ doCallRealMethod().when(rlsService).shouldRateLimit(any(), any());
+ when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount)))
+ .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
+ when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount)))
+ .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
+
+ RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder()
+ .addDescriptors(descriptor1)
+ .addDescriptors(descriptor2)
+ .setDomain(domain)
+ .setHitsAddend(acquireCount)
+ .build();
+ rlsService.shouldRateLimit(rateLimitRequest, streamObserver);
+
+ RateLimitResponse response = responseCapture.getValue();
+ assertEquals(Code.OK, response.getOverallCode());
+ response.getStatusesList()
+ .forEach(e -> assertEquals(Code.OK, e.getCode()));
+ }
+
+ @Test
+ public void testShouldRatePartialBlock() {
+ SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class);
+ StreamObserver streamObserver = mock(StreamObserver.class);
+ String domain = "testShouldRatePartialBlock";
+ int acquireCount = 1;
+
+ RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder()
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a1").setValue("b1").build())
+ .build();
+ RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder()
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a2").setValue("b2").build())
+ .addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("a3").setValue("b3").build())
+ .build();
+
+ ArgumentCaptor responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class);
+ doNothing().when(streamObserver)
+ .onNext(responseCapture.capture());
+
+ doCallRealMethod().when(rlsService).shouldRateLimit(any(), any());
+ when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount)))
+ .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.BLOCKED)));
+ when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount)))
+ .thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
+
+ RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder()
+ .addDescriptors(descriptor1)
+ .addDescriptors(descriptor2)
+ .setDomain(domain)
+ .setHitsAddend(acquireCount)
+ .build();
+ rlsService.shouldRateLimit(rateLimitRequest, streamObserver);
+
+ RateLimitResponse response = responseCapture.getValue();
+ assertEquals(Code.OVER_LIMIT, response.getOverallCode());
+ assertEquals(2, response.getStatusesCount());
+ assertTrue(response.getStatusesList().stream()
+ .anyMatch(e -> e.getCode().equals(Code.OVER_LIMIT)));
+ assertFalse(response.getStatusesList().stream()
+ .allMatch(e -> e.getCode().equals(Code.OVER_LIMIT)));
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverterTest.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverterTest.java
new file mode 100644
index 0000000000..554b38c717
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/test/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoySentinelRuleConverterTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule.KeyValueResource;
+import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoyRlsRule.ResourceDescriptor;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+
+import org.junit.Test;
+
+import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR;
+import static org.junit.Assert.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class EnvoySentinelRuleConverterTest {
+
+ @Test
+ public void testConvertToSentinelFlowRules() {
+ String domain = "testConvertToSentinelFlowRules";
+ EnvoyRlsRule rlsRule = new EnvoyRlsRule();
+ rlsRule.setDomain(domain);
+ List descriptors = new ArrayList<>();
+ ResourceDescriptor d1 = new ResourceDescriptor();
+ d1.setCount(10d);
+ d1.setResources(Collections.singleton(new KeyValueResource("k1", "v1")));
+ descriptors.add(d1);
+ ResourceDescriptor d2 = new ResourceDescriptor();
+ d2.setCount(20d);
+ d2.setResources(new HashSet<>(Arrays.asList(
+ new KeyValueResource("k2", "v2"),
+ new KeyValueResource("k3", "v3")
+ )));
+ descriptors.add(d2);
+ rlsRule.setDescriptors(descriptors);
+
+ List rules = EnvoySentinelRuleConverter.toSentinelFlowRules(rlsRule);
+ final String expectedK1 = domain + SEPARATOR + "k1" + SEPARATOR + "v1";
+ FlowRule r1 = rules.stream()
+ .filter(e -> e.getResource().equals(expectedK1))
+ .findAny()
+ .orElseThrow(() -> new AssertionError("the converted rule does not exist, expected key: " + expectedK1));
+ assertEquals(10d, r1.getCount(), 0.01);
+
+ final String expectedK2 = domain + SEPARATOR + "k2" + SEPARATOR + "v2" + SEPARATOR + "k3" + SEPARATOR + "v3";
+ FlowRule r2 = rules.stream()
+ .filter(e -> e.getResource().equals(expectedK2))
+ .findAny()
+ .orElseThrow(() -> new AssertionError("the converted rule does not exist, expected key: " + expectedK2));
+ assertEquals(20d, r2.getCount(), 0.01);
+ }
+}
diff --git a/sentinel-core/pom.xml b/sentinel-core/pom.xml
index 9228ee7e3e..1cb08f7d44 100755
--- a/sentinel-core/pom.xml
+++ b/sentinel-core/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.cspsentinel-parent
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOTsentinel-corejar
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
index 84b7c7e3c5..98ccb85a22 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java
@@ -52,7 +52,11 @@ void cleanCurrentEntryInLocal() {
((CtEntry)parent).child = null;
}
} else {
- throw new IllegalStateException("Bad async context state");
+ String curEntryName = curEntry == null ? "none"
+ : curEntry.resourceWrapper.getName() + "@" + curEntry.hashCode();
+ String msg = String.format("Bad async context state, expected entry: %s, but actual: %s",
+ getResourceWrapper().getName() + "@" + hashCode(), curEntryName);
+ throw new IllegalStateException(msg);
}
}
}
@@ -74,7 +78,8 @@ void initAsyncContext() {
.setOrigin(context.getOrigin())
.setCurEntry(this);
} else {
- RecordLog.warn("[AsyncEntry] Duplicate initialize of async context for entry: " + resourceWrapper.getName());
+ RecordLog.warn(
+ "[AsyncEntry] Duplicate initialize of async context for entry: " + resourceWrapper.getName());
}
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
index 6e728bb96e..c3bbadf091 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
@@ -15,22 +15,23 @@
*/
package com.alibaba.csp.sentinel;
-import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.EntranceNode;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
-import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.util.VersionUtil;
/**
+ * Universal constants of Sentinel.
+ *
* @author qinan.qn
* @author youji.zj
* @author jialiang.linjl
+ * @author Eric Zhao
*/
public final class Constants {
- public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.7.0");
+ public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.7.2");
public final static int MAX_CONTEXT_NAME_SIZE = 2000;
public final static int MAX_SLOT_CHAIN_SIZE = 6000;
@@ -57,18 +58,12 @@ public final class Constants {
* Global ROOT statistic node that represents the universal parent node.
*/
public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),
- new ClusterNode());
-
- /**
- * Global statistic node for inbound traffic. Usually used for {@link SystemRule} checking.
- */
- public final static ClusterNode ENTRY_NODE = new ClusterNode();
+ new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON));
/**
- * Response time that exceeds TIME_DROP_VALVE will be calculated as TIME_DROP_VALVE. Default value is 4900 ms.
- * It can be configured by property file or JVM parameter via {@code -Dcsp.sentinel.statistic.max.rt=xxx}.
+ * Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking.
*/
- public static final int TIME_DROP_VALVE = SentinelConfig.statisticMaxRt();
+ public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON);
/**
* The global switch for Sentinel.
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
index 0bb9c411f6..5c5b137b1d 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
@@ -71,7 +71,7 @@ private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrappe
}
if (context == null) {
// Using default context.
- context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
+ context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
// Global switch is turned off, so no rule checking will be done.
@@ -125,7 +125,7 @@ private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, bool
if (context == null) {
// Using default context.
- context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
+ context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
// Global switch is close, no rule checking will do.
@@ -245,8 +245,12 @@ static Map getChainMap() {
/**
* This class is used for skip context name checking.
*/
- private final static class MyContextUtil extends ContextUtil {
- static Context myEnter(String name, String origin, EntryType type) {
+ private final static class InternalContextUtil extends ContextUtil {
+ static Context internalEnter(String name) {
+ return trueEnter(name, "");
+ }
+
+ static Context internalEnter(String name, String origin) {
return trueEnter(name, origin);
}
}
@@ -329,4 +333,24 @@ public Entry entryWithPriority(String name, EntryType type, int count, boolean p
StringResourceWrapper resource = new StringResourceWrapper(name, type);
return entryWithPriority(resource, count, prioritized, args);
}
+
+ @Override
+ public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args)
+ throws BlockException {
+ return entryWithType(name, resourceType, entryType, count, false, args);
+ }
+
+ @Override
+ public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
+ Object[] args) throws BlockException {
+ StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
+ return entryWithPriority(resource, count, prioritized, args);
+ }
+
+ @Override
+ public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count,
+ boolean prioritized, Object[] args) throws BlockException {
+ StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
+ return asyncEntryWithPriorityInternal(resource, count, prioritized, args);
+ }
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/ResourceTypeConstants.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/ResourceTypeConstants.java
new file mode 100644
index 0000000000..1ae421cc58
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/ResourceTypeConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public final class ResourceTypeConstants {
+
+ public static final int COMMON = 0;
+ public static final int COMMON_WEB = 1;
+ public static final int COMMON_RPC = 2;
+ public static final int COMMON_API_GATEWAY = 3;
+ public static final int COMMON_DB_SQL = 4;
+
+ private ResourceTypeConstants() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java
index bedbd33bf3..32643b4697 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java
@@ -29,7 +29,7 @@
* @author leyou
* @author Eric Zhao
*/
-public interface Sph {
+public interface Sph extends SphResourceTypeSupport {
/**
* Create a protected resource.
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java
new file mode 100644
index 0000000000..4349473f6c
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel;
+
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public interface SphResourceTypeSupport {
+
+ /**
+ * Create a protected resource with provided classification.
+ *
+ * @param name the unique name of the protected resource
+ * @param resourceType the classification of the resource
+ * @param entryType the traffic entry type (IN/OUT) of the resource
+ * @param count tokens required
+ * @param args extra parameters
+ * @return new entry of the resource
+ * @throws BlockException if the block criteria is met
+ */
+ Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args)
+ throws BlockException;
+
+ /**
+ * Create a protected resource with provided classification.
+ *
+ * @param name the unique name of the protected resource
+ * @param resourceType the classification of the resource
+ * @param entryType the traffic entry type (IN/OUT) of the resource
+ * @param count tokens required
+ * @param prioritized whether the entry is prioritized
+ * @param args extra parameters
+ * @return new entry of the resource
+ * @throws BlockException if the block criteria is met
+ */
+ Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
+ Object[] args) throws BlockException;
+
+ /**
+ * Create an asynchronous resource with provided classification.
+ *
+ * @param name the unique name of the protected resource
+ * @param resourceType the classification of the resource
+ * @param entryType the traffic entry type (IN/OUT) of the resource
+ * @param count tokens required
+ * @param prioritized whether the entry is prioritized
+ * @param args extra parameters
+ * @return new entry of the resource
+ * @throws BlockException if the block criteria is met
+ */
+ AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
+ Object[] args) throws BlockException;
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java
index 696b686fe6..a7ad2aa19d 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java
@@ -76,6 +76,8 @@ public class SphU {
private static final Object[] OBJECTS0 = new Object[0];
+ private SphU() {}
+
/**
* Checking all {@link Rule}s about the resource.
*
@@ -246,7 +248,7 @@ public static AsyncEntry asyncEntry(String name, EntryType type, int count, Obje
/**
* Checking all {@link Rule}s related the resource. The entry is prioritized.
*
- * @param name the unique name for the protected resource
+ * @param name the unique name for the protected resource
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
* @since 1.4.0
*/
@@ -257,14 +259,97 @@ public static Entry entryWithPriority(String name) throws BlockException {
/**
* Checking all {@link Rule}s related the resource. The entry is prioritized.
*
- * @param name the unique name for the protected resource
- * @param type the resource is an inbound or an outbound method. This is used
- * to mark whether it can be blocked when the system is unstable,
- * only inbound traffic could be blocked by {@link SystemRule}
+ * @param name the unique name for the protected resource
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
* @since 1.4.0
*/
public static Entry entryWithPriority(String name, EntryType type) throws BlockException {
return Env.sph.entryWithPriority(name, type, 1, true);
}
+
+ /**
+ * Record statistics and check all rules of the resource.
+ *
+ * @param name the unique name for the protected resource
+ * @param resourceType classification of the resource (e.g. Web or RPC)
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
+ * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded
+ * @since 1.7.0
+ */
+ public static Entry entry(String name, int resourceType, EntryType type) throws BlockException {
+ return Env.sph.entryWithType(name, resourceType, type, 1, OBJECTS0);
+ }
+
+ /**
+ * Record statistics and check all rules of the resource.
+ *
+ * @param name the unique name for the protected resource
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
+ * @param resourceType classification of the resource (e.g. Web or RPC)
+ * @param args extra parameters.
+ * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded
+ * @since 1.7.0
+ */
+ public static Entry entry(String name, int resourceType, EntryType type, Object[] args)
+ throws BlockException {
+ return Env.sph.entryWithType(name, resourceType, type, 1, args);
+ }
+
+ /**
+ * Record statistics and check all rules of the resource.
+ *
+ * @param name the unique name for the protected resource
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
+ * @param resourceType classification of the resource (e.g. Web or RPC)
+ * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded
+ * @since 1.7.0
+ */
+ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type)
+ throws BlockException {
+ return Env.sph.asyncEntryWithType(name, resourceType, type, 1, false, OBJECTS0);
+ }
+
+ /**
+ * Record statistics and check all rules of the resource.
+ *
+ * @param name the unique name for the protected resource
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
+ * @param resourceType classification of the resource (e.g. Web or RPC)
+ * @param args extra parameters
+ * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded
+ * @since 1.7.0
+ */
+ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type, Object[] args)
+ throws BlockException {
+ return Env.sph.asyncEntryWithType(name, resourceType, type, 1, false, args);
+ }
+
+ /**
+ * Record statistics and check all rules of the resource.
+ *
+ * @param name the unique name for the protected resource
+ * @param type the resource is an inbound or an outbound method. This is used
+ * to mark whether it can be blocked when the system is unstable,
+ * only inbound traffic could be blocked by {@link SystemRule}
+ * @param resourceType classification of the resource (e.g. Web or RPC)
+ * @param acquireCount tokens required
+ * @param args extra parameters
+ * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded
+ * @since 1.7.0
+ */
+ public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type, int acquireCount,
+ Object[] args) throws BlockException {
+ return Env.sph.asyncEntryWithType(name, resourceType, type, acquireCount, false, args);
+ }
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
index a0fa11d675..95f6584841 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java
@@ -40,6 +40,12 @@
*/
EntryType entryType() default EntryType.OUT;
+ /**
+ * @return the classification (type) of the resource
+ * @since 1.7.0
+ */
+ int resourceType() default 0;
+
/**
* @return name of the block exception function, empty by default
*/
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java
index 5079191238..a0d7c2268d 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java
@@ -15,26 +15,23 @@
*/
package com.alibaba.csp.sentinel.config;
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import com.alibaba.csp.sentinel.log.LogBase;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AppNameUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
/**
- * The universal local config center of Sentinel. The config is retrieved from command line arguments
- * and {@code ${user.home}/logs/csp/${appName}.properties} file by default.
+ * The universal local configuration center of Sentinel. The config is retrieved from command line arguments
+ * and customized properties file by default.
*
* @author leyou
* @author Eric Zhao
*/
-public class SentinelConfig {
+public final class SentinelConfig {
/**
* The default application type.
@@ -52,18 +49,19 @@ public class SentinelConfig {
public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count";
public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor";
public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt";
+ public static final String SPI_CLASSLOADER = "csp.sentinel.spi.classloader";
static final String DEFAULT_CHARSET = "UTF-8";
static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50;
static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6;
static final int DEFAULT_COLD_FACTOR = 3;
- static final int DEFAULT_STATISTIC_MAX_RT = 4900;
+
+ public static final int DEFAULT_STATISTIC_MAX_RT = 4900;
static {
try {
initialize();
loadProps();
-
resolveAppType();
RecordLog.info("[SentinelConfig] Application type resolved: " + appType);
} catch (Throwable ex) {
@@ -90,48 +88,17 @@ private static void resolveAppType() {
private static void initialize() {
// Init default properties.
- SentinelConfig.setConfig(CHARSET, DEFAULT_CHARSET);
- SentinelConfig.setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE));
- SentinelConfig.setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT));
- SentinelConfig.setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR));
- SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
+ setConfig(CHARSET, DEFAULT_CHARSET);
+ setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE));
+ setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT));
+ setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR));
+ setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
}
private static void loadProps() {
- // Resolve app name.
- AppNameUtil.resolveAppName();
- try {
- String appName = AppNameUtil.getAppName();
- if (appName == null) {
- appName = "";
- }
- // We first retrieve the properties from the property file.
- String fileName = LogBase.getLogBaseDir() + appName + ".properties";
- File file = new File(fileName);
- if (file.exists()) {
- RecordLog.info("[SentinelConfig] Reading config from " + fileName);
- FileInputStream fis = new FileInputStream(fileName);
- Properties fileProps = new Properties();
- fileProps.load(fis);
- fis.close();
-
- for (Object key : fileProps.keySet()) {
- SentinelConfig.setConfig((String)key, (String)fileProps.get(key));
- }
- }
- } catch (Throwable ioe) {
- RecordLog.info(ioe.getMessage(), ioe);
- }
-
- // JVM parameter override file config.
- for (Map.Entry entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) {
- String configKey = entry.getKey().toString();
- String configValue = entry.getValue().toString();
- String configValueOld = getConfig(configKey);
- SentinelConfig.setConfig(configKey, configValue);
- if (configValueOld != null) {
- RecordLog.info("[SentinelConfig] JVM parameter overrides {0}: {1} -> {2}", configKey, configValueOld, configValue);
- }
+ Properties properties = SentinelConfigLoader.getProperties();
+ for (Object key : properties.keySet()) {
+ setConfig((String) key, (String) properties.get(key));
}
}
@@ -189,7 +156,7 @@ public static long singleMetricFileSize() {
return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE));
} catch (Throwable throwable) {
RecordLog.warn("[SentinelConfig] Parse singleMetricFileSize fail, use default value: "
- + DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable);
+ + DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable);
return DEFAULT_SINGLE_METRIC_FILE_SIZE;
}
}
@@ -199,7 +166,7 @@ public static int totalMetricFileCount() {
return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT));
} catch (Throwable throwable) {
RecordLog.warn("[SentinelConfig] Parse totalMetricFileCount fail, use default value: "
- + DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable);
+ + DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable);
return DEFAULT_TOTAL_METRIC_FILE_COUNT;
}
}
@@ -221,13 +188,28 @@ public static int coldFactor() {
}
}
+ /**
+ *
Get the max RT value that Sentinel could accept.
+ *
Response time that exceeds {@code statisticMaxRt} will be recorded as this value.
+ * The default value is {@link #DEFAULT_STATISTIC_MAX_RT}.
+ *
+ * @return the max allowed RT value
+ * @since 1.4.1
+ */
public static int statisticMaxRt() {
+ String v = props.get(STATISTIC_MAX_RT);
try {
- return Integer.parseInt(props.get(STATISTIC_MAX_RT));
+ if (StringUtil.isEmpty(v)) {
+ return DEFAULT_STATISTIC_MAX_RT;
+ }
+ return Integer.parseInt(v);
} catch (Throwable throwable) {
- RecordLog.warn("[SentinelConfig] Parse statisticMaxRt fail, use default value: "
- + DEFAULT_STATISTIC_MAX_RT, throwable);
+ RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {0}, using the default value instead: "
+ + DEFAULT_STATISTIC_MAX_RT, v, throwable);
+ SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT));
return DEFAULT_STATISTIC_MAX_RT;
}
}
+
+ private SentinelConfig() {}
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java
new file mode 100644
index 0000000000..67cd4805df
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.config;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.AppNameUtil;
+import com.alibaba.csp.sentinel.util.ConfigUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
+
+/**
+ *
The loader that responsible for loading Sentinel common configurations.
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public final class SentinelConfigLoader {
+
+ public static final String SENTINEL_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE";
+ public static final String SENTINEL_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file";
+
+ private static final String DIR_NAME = "logs" + File.separator + "csp";
+ private static final String USER_HOME = "user.home";
+
+ private static final String DEFAULT_SENTINEL_CONFIG_FILE = "classpath:sentinel.properties";
+
+ private static Properties properties = new Properties();
+
+ static {
+ try {
+ load();
+ } catch (Throwable t) {
+ RecordLog.warn("[SentinelConfigLoader] Failed to initialize configuration items", t);
+ }
+ }
+
+ private static void load() {
+ // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path
+ String fileName = System.getProperty(SENTINEL_CONFIG_PROPERTY_KEY);
+ if (StringUtil.isBlank(fileName)) {
+ fileName = System.getenv(SENTINEL_CONFIG_ENV_KEY);
+ if (StringUtil.isBlank(fileName)) {
+ fileName = DEFAULT_SENTINEL_CONFIG_FILE;
+ }
+ }
+
+ Properties p = ConfigUtil.loadProperties(fileName);
+
+ // Compatible with legacy config file path.
+ if (p == null) {
+ String path = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator;
+ fileName = path + AppNameUtil.getAppName() + ".properties";
+ File file = new File(fileName);
+ if (file.exists()) {
+ p = ConfigUtil.loadProperties(fileName);
+ }
+ }
+
+ if (p != null && !p.isEmpty()) {
+ RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from " + fileName);
+ properties.putAll(p);
+ }
+
+ for (Map.Entry entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) {
+ String configKey = entry.getKey().toString();
+ String newConfigValue = entry.getValue().toString();
+ String oldConfigValue = properties.getProperty(configKey);
+ properties.put(configKey, newConfigValue);
+ if (oldConfigValue != null) {
+ RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {0}: {1} -> {2}",
+ configKey, oldConfigValue, newConfigValue);
+ }
+ }
+ }
+
+
+ public static Properties getProperties() {
+ return properties;
+ }
+
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java
index 0d84ad78fb..e68fd599e3 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java
@@ -21,6 +21,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil;
/**
* Load registered init functions and execute in order.
@@ -42,7 +43,7 @@ public static void doInit() {
return;
}
try {
- ServiceLoader loader = ServiceLoader.load(InitFunc.class);
+ ServiceLoader loader = ServiceLoaderUtil.getServiceLoader(InitFunc.class);
List initList = new ArrayList();
for (InitFunc initFunc : loader) {
RecordLog.info("[InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName());
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java
new file mode 100644
index 0000000000..7654fa2a03
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.log;
+
+import java.io.UnsupportedEncodingException;
+import java.util.logging.*;
+
+/**
+ * This Handler publishes log records to console by using {@link java.util.logging.StreamHandler}.
+ *
+ * Print log of WARNING level or above to System.err,
+ * and print log of INFO level or below to System.out.
+ *
+ * To use this handler, add the following VM argument:
+ *
+ * -Dcsp.sentinel.log.output.type=console
+ *
+ *
+ * @author cdfive
+ */
+class ConsoleHandler extends Handler {
+
+ /**
+ * A Handler which publishes log records to System.out.
+ */
+ private StreamHandler stdoutHandler;
+
+ /**
+ * A Handler which publishes log records to System.err.
+ */
+ private StreamHandler stderrHandler;
+
+ public ConsoleHandler() {
+ this.stdoutHandler = new StreamHandler(System.out, new CspFormatter());
+ this.stderrHandler = new StreamHandler(System.err, new CspFormatter());
+ }
+
+ @Override
+ public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
+ this.stdoutHandler.setFormatter(newFormatter);
+ this.stderrHandler.setFormatter(newFormatter);
+ }
+
+ @Override
+ public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException {
+ this.stdoutHandler.setEncoding(encoding);
+ this.stderrHandler.setEncoding(encoding);
+ }
+
+ @Override
+ public void publish(LogRecord record) {
+ if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
+ stderrHandler.publish(record);
+ stderrHandler.flush();
+ } else {
+ stdoutHandler.publish(record);
+ stdoutHandler.flush();
+ }
+ }
+
+ @Override
+ public void flush() {
+ stdoutHandler.flush();
+ stderrHandler.flush();
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ stdoutHandler.close();
+ stderrHandler.close();
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java
index b2a8897962..5d24c21217 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java
@@ -28,7 +28,7 @@ class CspFormatter extends Formatter {
private final ThreadLocal dateFormatThreadLocal = new ThreadLocal() {
@Override
public SimpleDateFormat initialValue() {
- return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}
};
@@ -37,6 +37,7 @@ public String format(LogRecord record) {
final DateFormat df = dateFormatThreadLocal.get();
StringBuilder builder = new StringBuilder(1000);
builder.append(df.format(new Date(record.getMillis()))).append(" ");
+ builder.append(record.getLevel().getName()).append(" ");
builder.append(formatMessage(record));
String throwable = "";
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java
index ae71b1a415..2de0bddd4f 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java
@@ -15,83 +15,136 @@
*/
package com.alibaba.csp.sentinel.log;
+import com.alibaba.csp.sentinel.util.PidUtil;
+
import java.io.File;
import java.io.IOException;
+import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
-import com.alibaba.csp.sentinel.util.PidUtil;
+import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
/**
- * Default log base dir is ${user.home}/logs/csp/, we can use {@link #LOG_DIR} System property to override it.
- * Default log file name dose not contain pid, but if multi instances of the same app are running in the same
- * machine, we may want to distinguish the log file by pid number, in this case, {@link #LOG_NAME_USE_PID}
- * System property could be configured as "true" to turn on this switch.
+ *
The base class for logging.
+ *
+ *
+ * The default log base directory is {@code ${user.home}/logs/csp/}. We can use the {@link #LOG_DIR}
+ * property to override it. The default log file name dose not contain pid, but if multi-instances of the same service
+ * are running in the same machine, we may want to distinguish the log file by process ID number.
+ * In this case, {@link #LOG_NAME_USE_PID} property could be configured as "true" to turn on this switch.
+ *
*
* @author leyou
*/
public class LogBase {
- public static final String LOG_CHARSET = "utf-8";
+ public static final String LOG_DIR = "csp.sentinel.log.dir";
+ public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid";
+ public static final String LOG_OUTPUT_TYPE = "csp.sentinel.log.output.type";
+ public static final String LOG_CHARSET = "csp.sentinel.log.charset";
+
+ /**
+ * Output biz log (e.g. RecordLog and CommandCenterLog) to file.
+ */
+ public static final String LOG_OUTPUT_TYPE_FILE = "file";
+ /**
+ * Output biz log (e.g. RecordLog and CommandCenterLog) to console.
+ */
+ public static final String LOG_OUTPUT_TYPE_CONSOLE = "console";
+ public static final String LOG_CHARSET_UTF8 = "utf-8";
private static final String DIR_NAME = "logs" + File.separator + "csp";
private static final String USER_HOME = "user.home";
- public static final String LOG_DIR = "csp.sentinel.log.dir";
- public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid";
-
- private static boolean logNameUsePid = false;
+ private static boolean logNameUsePid;
+ private static String logOutputType;
private static String logBaseDir;
+ private static String logCharSet;
static {
try {
- init();
+ initialize();
+ loadProperties();
} catch (Throwable t) {
System.err.println("[LogBase] FATAL ERROR when initializing log class");
t.printStackTrace();
}
}
- private static void init() {
- // first use -D, then use user home.
- String logDir = System.getProperty(LOG_DIR);
+ private static void initialize() {
+ logNameUsePid = false;
+ logOutputType = LOG_OUTPUT_TYPE_FILE;
+ logBaseDir = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator;
+ logCharSet = LOG_CHARSET_UTF8;
+ }
+
+ private static void loadProperties() {
+ Properties properties = LogConfigLoader.getProperties();
- if (logDir == null || logDir.isEmpty()) {
- logDir = System.getProperty(USER_HOME);
- logDir = addSeparator(logDir) + DIR_NAME + File.separator;
+ logOutputType = properties.get(LOG_OUTPUT_TYPE) == null ? logOutputType : properties.getProperty(LOG_OUTPUT_TYPE);
+ if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) {
+ logOutputType = LOG_OUTPUT_TYPE_FILE;
}
- logDir = addSeparator(logDir);
- File dir = new File(logDir);
+ System.out.println("INFO: log output type is: " + logOutputType);
+
+ logCharSet = properties.getProperty(LOG_CHARSET) == null ? logCharSet : properties.getProperty(LOG_CHARSET);
+ System.out.println("INFO: log charset is: " + logCharSet);
+
+
+ logBaseDir = properties.getProperty(LOG_DIR) == null ? logBaseDir : properties.getProperty(LOG_DIR);
+ logBaseDir = addSeparator(logBaseDir);
+ File dir = new File(logBaseDir);
if (!dir.exists()) {
if (!dir.mkdirs()) {
- System.err.println("ERROR: create log base dir error: " + logDir);
+ System.err.println("ERROR: create log base dir error: " + logBaseDir);
}
}
- // logBaseDir must end with File.separator
- logBaseDir = logDir;
System.out.println("INFO: log base dir is: " + logBaseDir);
- String usePid = System.getProperty(LOG_NAME_USE_PID, "");
+
+ String usePid = properties.getProperty(LOG_NAME_USE_PID);
logNameUsePid = "true".equalsIgnoreCase(usePid);
System.out.println("INFO: log name use pid is: " + logNameUsePid);
}
+
/**
- * Whether log file name should contain pid. This switch is configured by {@link #LOG_NAME_USE_PID} System property.
+ * Whether log file name should contain pid. This switch is configured by {@link #LOG_NAME_USE_PID} system property.
*
- * @return if log file name should contain pid, return true, otherwise return false.
+ * @return true if log file name should contain pid, return true, otherwise false
*/
public static boolean isLogNameUsePid() {
return logNameUsePid;
}
- private static String addSeparator(String logDir) {
- if (!logDir.endsWith(File.separator)) {
- logDir += File.separator;
- }
- return logDir;
+ /**
+ * Get the log file base directory path, which is guaranteed ended with {@link File#separator}.
+ *
+ * @return log file base directory path
+ */
+ public static String getLogBaseDir() {
+ return logBaseDir;
+ }
+
+ /**
+ * Get the log file output type.
+ *
+ * @return log output type, "file" by default
+ */
+ public static String getLogOutputType() {
+ return logOutputType;
+ }
+
+ /**
+ * Get the log file charset.
+ *
+ * @return the log file charset, "utf-8" by default
+ */
+ public static String getLogCharset() {
+ return logCharSet;
}
protected static void log(Logger logger, Handler handler, Level level, String detail, Object... params) {
@@ -114,33 +167,45 @@ protected static void log(Logger logger, Handler handler, Level level, String de
logger.log(level, detail, throwable);
}
- /**
- * Get log file base directory path, the returned path is guaranteed end with {@link File#separator}
- *
- * @return log file base directory path.
- */
- public static String getLogBaseDir() {
- return logBaseDir;
- }
protected static Handler makeLogger(String logName, Logger heliumRecordLog) {
CspFormatter formatter = new CspFormatter();
- String fileName = LogBase.getLogBaseDir() + logName;
- if (isLogNameUsePid()) {
- fileName += ".pid" + PidUtil.getPid();
- }
+
Handler handler = null;
- try {
- handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true);
- handler.setFormatter(formatter);
- handler.setEncoding(LOG_CHARSET);
- } catch (IOException e) {
- e.printStackTrace();
+
+ // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET
+ switch (logOutputType) {
+ case LOG_OUTPUT_TYPE_FILE:
+ String fileName = LogBase.getLogBaseDir() + logName;
+ if (isLogNameUsePid()) {
+ fileName += ".pid" + PidUtil.getPid();
+ }
+ try {
+ handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true);
+ handler.setFormatter(formatter);
+ handler.setEncoding(logCharSet);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ break;
+ case LOG_OUTPUT_TYPE_CONSOLE:
+ try {
+ handler = new ConsoleHandler();
+ handler.setFormatter(formatter);
+ handler.setEncoding(logCharSet);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ break;
+ default:
+ break;
}
+
if (handler != null) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, handler);
}
heliumRecordLog.setLevel(Level.ALL);
return handler;
}
+
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogConfigLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogConfigLoader.java
new file mode 100644
index 0000000000..3cdb541bb1
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogConfigLoader.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.log;
+
+import com.alibaba.csp.sentinel.util.ConfigUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ *
The loader that responsible for loading Sentinel log configurations.
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class LogConfigLoader {
+
+ public static final String LOG_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE";
+ public static final String LOG_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file";
+
+ private static final String DEFAULT_LOG_CONFIG_FILE = "classpath:sentinel.properties";
+
+ private static final Properties properties = new Properties();
+
+ static {
+ try {
+ load();
+ } catch (Throwable t) {
+ // NOTE: do not use RecordLog here, or there will be circular class dependency!
+ System.err.println("[LogConfigLoader] Failed to initialize configuration items");
+ t.printStackTrace();
+ }
+ }
+
+ private static void load() {
+ // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path
+ String fileName = System.getProperty(LOG_CONFIG_PROPERTY_KEY);
+ if (StringUtil.isBlank(fileName)) {
+ fileName = System.getenv(LOG_CONFIG_ENV_KEY);
+ if (StringUtil.isBlank(fileName)) {
+ fileName = DEFAULT_LOG_CONFIG_FILE;
+ }
+ }
+
+ Properties p = ConfigUtil.loadProperties(fileName);
+ if (p != null && !p.isEmpty()) {
+ properties.putAll(p);
+ }
+
+ CopyOnWriteArraySet> copy = new CopyOnWriteArraySet<>(System.getProperties().entrySet());
+ for (Map.Entry entry : copy) {
+ String configKey = entry.getKey().toString();
+ String newConfigValue = entry.getValue().toString();
+ properties.put(configKey, newConfigValue);
+ }
+ }
+
+ public static Properties getProperties() {
+ return properties;
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java
index c342697de2..6f5f4ef4f8 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java
@@ -19,8 +19,10 @@
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.util.AssertUtil;
/**
*
@@ -42,6 +44,19 @@
*/
public class ClusterNode extends StatisticNode {
+ private final String name;
+ private final int resourceType;
+
+ public ClusterNode(String name) {
+ this(name, ResourceTypeConstants.COMMON);
+ }
+
+ public ClusterNode(String name, int resourceType) {
+ AssertUtil.notEmpty(name, "name cannot be empty");
+ this.name = name;
+ this.resourceType = resourceType;
+ }
+
/**
*
The origin map holds the pair: (origin, originNode) for one specific resource.
*
@@ -50,10 +65,30 @@ public class ClusterNode extends StatisticNode {
* at the very beginning while concurrent map will hold the lock all the time.
*
*/
- private Map originCountMap = new HashMap();
+ private Map originCountMap = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock();
+ /**
+ * Get resource name of the resource node.
+ *
+ * @return resource name
+ * @since 1.7.0
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get classification (type) of the resource.
+ *
+ * @return resource type
+ * @since 1.7.0
+ */
+ public int getResourceType() {
+ return resourceType;
+ }
+
/**
*
Get {@link Node} of the specific origin. Usually the origin is the Service Consumer's app name.
*
If the origin node for given origin is absent, then a new {@link StatisticNode}
@@ -84,7 +119,7 @@ public Node getOrCreateOriginNode(String origin) {
return statisticNode;
}
- public synchronized Map getOriginCountMap() {
+ public Map getOriginCountMap() {
return originCountMap;
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java
index 0673915499..0927d8e43c 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java
@@ -15,11 +15,13 @@
*/
package com.alibaba.csp.sentinel.node;
+import java.util.List;
import java.util.Map;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.node.metric.MetricNode;
import com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport;
+import com.alibaba.csp.sentinel.util.function.Predicate;
/**
* Holds real-time statistics for resources.
@@ -146,6 +148,15 @@ public interface Node extends OccupySupport, DebugSupport {
*/
Map metrics();
+ /**
+ * Fetch all raw metric items that satisfies the time predicate.
+ *
+ * @param timePredicate time predicate
+ * @return raw metric items that satisfies the time predicate
+ * @since 1.7.0
+ */
+ List rawMetricsInMin(Predicate timePredicate);
+
/**
* Add pass count.
*
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java
index 80a8443cad..a722269afd 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/StatisticNode.java
@@ -20,6 +20,7 @@
import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric;
import com.alibaba.csp.sentinel.slots.statistic.metric.Metric;
import com.alibaba.csp.sentinel.util.TimeUtil;
+import com.alibaba.csp.sentinel.util.function.Predicate;
import java.util.List;
import java.util.Map;
@@ -131,6 +132,11 @@ public Map metrics() {
return metrics;
}
+ @Override
+ public List rawMetricsInMin(Predicate timePredicate) {
+ return rollingCounterInMinute.detailsOnCondition(timePredicate);
+ }
+
private boolean isNodeInTime(MetricNode node, long currentTime) {
return node.getTimestamp() > lastFetchTime && node.getTimestamp() < currentTime;
}
@@ -207,7 +213,8 @@ public double successQps() {
@Override
public double maxSuccessQps() {
- return rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount();
+ return (double) rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount()
+ / rollingCounterInSecond.getWindowIntervalInSec();
}
@Override
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricNode.java
index e1a6707c27..2f8de6bc2f 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricNode.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricNode.java
@@ -27,6 +27,13 @@
*/
public class MetricNode {
+ private String resource;
+ /**
+ * Resource classification (e.g. SQL or RPC)
+ * @since 1.7.0
+ */
+ private int classification;
+
private long timestamp;
private long passQps;
private long blockQps;
@@ -38,8 +45,10 @@ public class MetricNode {
* @since 1.5.0
*/
private long occupiedPassQps;
-
- private String resource;
+ /**
+ * @since 1.7.0
+ */
+ private int concurrency;
public long getTimestamp() {
return timestamp;
@@ -105,12 +114,38 @@ public void setResource(String resource) {
this.resource = resource;
}
+ public int getClassification() {
+ return classification;
+ }
+
+ public MetricNode setClassification(int classification) {
+ this.classification = classification;
+ return this;
+ }
+
+ public int getConcurrency() {
+ return concurrency;
+ }
+
+ public MetricNode setConcurrency(int concurrency) {
+ this.concurrency = concurrency;
+ return this;
+ }
+
@Override
public String toString() {
- return "MetricNode{" + "timestamp=" + timestamp + ", passQps=" + passQps + ", blockQps=" + blockQps
- + ", successQps=" + successQps + ", exceptionQps=" + exceptionQps + ", rt=" + rt
- + ", occupiedPassQps=" + occupiedPassQps + ", resource='"
- + resource + '\'' + '}';
+ return "MetricNode{" +
+ "resource='" + resource + '\'' +
+ ", classification=" + classification +
+ ", timestamp=" + timestamp +
+ ", passQps=" + passQps +
+ ", blockQps=" + blockQps +
+ ", successQps=" + successQps +
+ ", exceptionQps=" + exceptionQps +
+ ", rt=" + rt +
+ ", concurrency=" + concurrency +
+ ", occupiedPassQps=" + occupiedPassQps +
+ '}';
}
/**
@@ -132,7 +167,9 @@ public String toThinString() {
sb.append(successQps).append("|");
sb.append(exceptionQps).append("|");
sb.append(rt).append("|");
- sb.append(occupiedPassQps);
+ sb.append(occupiedPassQps).append("|");
+ sb.append(concurrency).append("|");
+ sb.append(classification);
return sb.toString();
}
@@ -152,9 +189,15 @@ public static MetricNode fromThinString(String line) {
node.setSuccessQps(Long.parseLong(strs[4]));
node.setExceptionQps(Long.parseLong(strs[5]));
node.setRt(Long.parseLong(strs[6]));
- if (strs.length == 8) {
+ if (strs.length >= 8) {
node.setOccupiedPassQps(Long.parseLong(strs[7]));
}
+ if (strs.length >= 9) {
+ node.setConcurrency(Integer.parseInt(strs[8]));
+ }
+ if (strs.length == 10) {
+ node.setClassification(Integer.parseInt(strs[9]));
+ }
return node;
}
@@ -180,7 +223,9 @@ public String toFatString() {
sb.append(getSuccessQps()).append("|");
sb.append(getExceptionQps()).append("|");
sb.append(getRt()).append("|");
- sb.append(getOccupiedPassQps());
+ sb.append(getOccupiedPassQps()).append("|");
+ sb.append(concurrency).append("|");
+ sb.append(classification);
sb.append('\n');
return sb.toString();
}
@@ -202,9 +247,15 @@ public static MetricNode fromFatString(String line) {
node.setSuccessQps(Long.parseLong(strs[5]));
node.setExceptionQps(Long.parseLong(strs[6]));
node.setRt(Long.parseLong(strs[7]));
- if (strs.length == 9) {
+ if (strs.length >= 9) {
node.setOccupiedPassQps(Long.parseLong(strs[8]));
}
+ if (strs.length >= 10) {
+ node.setConcurrency(Integer.parseInt(strs[9]));
+ }
+ if (strs.length == 11) {
+ node.setClassification(Integer.parseInt(strs[10]));
+ }
return node;
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java
index 4e3d3a1ef8..93992cc9b6 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java
@@ -38,14 +38,13 @@ public class MetricTimerListener implements Runnable {
@Override
public void run() {
- Map> maps = new TreeMap>();
+ Map> maps = new TreeMap<>();
for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) {
- String name = e.getKey().getName();
ClusterNode node = e.getValue();
Map metrics = node.metrics();
- aggregate(maps, metrics, name);
+ aggregate(maps, metrics, node);
}
- aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.TOTAL_IN_RESOURCE_NAME);
+ aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.ENTRY_NODE);
if (!maps.isEmpty()) {
for (Entry> entry : maps.entrySet()) {
try {
@@ -57,11 +56,12 @@ public void run() {
}
}
- private void aggregate(Map> maps, Map metrics, String resourceName) {
+ private void aggregate(Map> maps, Map metrics, ClusterNode node) {
for (Entry entry : metrics.entrySet()) {
long time = entry.getKey();
MetricNode metricNode = entry.getValue();
- metricNode.setResource(resourceName);
+ metricNode.setResource(node.getName());
+ metricNode.setClassification(node.getResourceType());
if (maps.get(time) == null) {
maps.put(time, new ArrayList());
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java
index 91e20a4c5d..5f89daf7e4 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java
@@ -18,6 +18,7 @@
import java.lang.reflect.Method;
import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
import com.alibaba.csp.sentinel.util.IdUtil;
import com.alibaba.csp.sentinel.util.MethodUtil;
@@ -28,17 +29,15 @@
*/
public class MethodResourceWrapper extends ResourceWrapper {
- private transient Method method;
+ private final transient Method method;
- public MethodResourceWrapper(Method method, EntryType type) {
- this.method = method;
- this.name = MethodUtil.resolveMethodName(method);
- this.type = type;
+ public MethodResourceWrapper(Method method, EntryType e) {
+ this(method, e, ResourceTypeConstants.COMMON);
}
- @Override
- public String getName() {
- return name;
+ public MethodResourceWrapper(Method method, EntryType e, int resType) {
+ super(MethodUtil.resolveMethodName(method), e, resType);
+ this.method = method;
}
public Method getMethod() {
@@ -47,12 +46,15 @@ public Method getMethod() {
@Override
public String getShowName() {
- return IdUtil.truncate(this.name);
+ return name;
}
@Override
- public EntryType getType() {
- return type;
+ public String toString() {
+ return "MethodResourceWrapper{" +
+ "name='" + name + '\'' +
+ ", entryType=" + entryType +
+ ", resourceType=" + resourceType +
+ '}';
}
-
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java
index a796344c89..a3eb0196f1 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java
@@ -16,28 +16,64 @@
package com.alibaba.csp.sentinel.slotchain;
import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.util.AssertUtil;
/**
- * A wrapper of resource name and {@link EntryType}.
+ * A wrapper of resource name and type.
*
* @author qinan.qn
* @author jialiang.linjl
+ * @author Eric Zhao
*/
public abstract class ResourceWrapper {
- protected String name;
- protected EntryType type = EntryType.OUT;
+ protected final String name;
- public abstract String getName();
+ protected final EntryType entryType;
+ protected final int resourceType;
- public abstract String getShowName();
+ public ResourceWrapper(String name, EntryType entryType, int resourceType) {
+ AssertUtil.notEmpty(name, "resource name cannot be empty");
+ AssertUtil.notNull(entryType, "entryType cannot be null");
+ this.name = name;
+ this.entryType = entryType;
+ this.resourceType = resourceType;
+ }
+
+ /**
+ * Get the resource name.
+ *
+ * @return the resource name
+ */
+ public String getName() {
+ return name;
+ }
/**
* Get {@link EntryType} of this wrapper.
*
* @return {@link EntryType} of this wrapper.
*/
- public abstract EntryType getType();
+ public EntryType getEntryType() {
+ return entryType;
+ }
+
+ /**
+ * Get the classification of this resource.
+ *
+ * @return the classification of this resource
+ * @since 1.7.0
+ */
+ public int getResourceType() {
+ return resourceType;
+ }
+
+ /**
+ * Get the beautified resource name to be showed.
+ *
+ * @return the beautified resource name
+ */
+ public abstract String getShowName();
/**
* Only {@link #getName()} is considered.
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java
index dedad3c19d..9aabbb3376 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java
@@ -15,12 +15,9 @@
*/
package com.alibaba.csp.sentinel.slotchain;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ServiceLoader;
-
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
+import com.alibaba.csp.sentinel.util.SpiLoader;
/**
* A provider for creating slot chains via resolved slot chain builder SPI.
@@ -30,9 +27,7 @@
*/
public final class SlotChainProvider {
- private static volatile SlotChainBuilder builder = null;
-
- private static final ServiceLoader LOADER = ServiceLoader.load(SlotChainBuilder.class);
+ private static volatile SlotChainBuilder slotChainBuilder = null;
/**
* The load and pick process is not thread-safe, but it's okay since the method should be only invoked
@@ -41,37 +36,22 @@ public final class SlotChainProvider {
* @return new created slot chain
*/
public static ProcessorSlotChain newSlotChain() {
- if (builder != null) {
- return builder.build();
+ if (slotChainBuilder != null) {
+ return slotChainBuilder.build();
}
- resolveSlotChainBuilder();
+ // Resolve the slot chain builder SPI.
+ slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);
- if (builder == null) {
+ if (slotChainBuilder == null) {
+ // Should not go through here.
RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
- builder = new DefaultSlotChainBuilder();
- }
- return builder.build();
- }
-
- private static void resolveSlotChainBuilder() {
- List list = new ArrayList();
- boolean hasOther = false;
- for (SlotChainBuilder builder : LOADER) {
- if (builder.getClass() != DefaultSlotChainBuilder.class) {
- hasOther = true;
- list.add(builder);
- }
- }
- if (hasOther) {
- builder = list.get(0);
+ slotChainBuilder = new DefaultSlotChainBuilder();
} else {
- // No custom builder, using default.
- builder = new DefaultSlotChainBuilder();
+ RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
+ + slotChainBuilder.getClass().getCanonicalName());
}
-
- RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
- + builder.getClass().getCanonicalName());
+ return slotChainBuilder.build();
}
private SlotChainProvider() {}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java
index d45f088258..dd4ff6919e 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java
@@ -16,26 +16,22 @@
package com.alibaba.csp.sentinel.slotchain;
import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
/**
- * Common resource wrapper.
+ * Common string resource wrapper.
*
* @author qinan.qn
* @author jialiang.linjl
*/
public class StringResourceWrapper extends ResourceWrapper {
- public StringResourceWrapper(String name, EntryType type) {
- if (name == null) {
- throw new IllegalArgumentException("Resource name cannot be null");
- }
- this.name = name;
- this.type = type;
+ public StringResourceWrapper(String name, EntryType e) {
+ super(name, e, ResourceTypeConstants.COMMON);
}
- @Override
- public String getName() {
- return name;
+ public StringResourceWrapper(String name, EntryType e, int resType) {
+ super(name, e, resType);
}
@Override
@@ -43,16 +39,12 @@ public String getShowName() {
return name;
}
- @Override
- public EntryType getType() {
- return type;
- }
-
@Override
public String toString() {
return "StringResourceWrapper{" +
"name='" + name + '\'' +
- ", type=" + type +
+ ", entryType=" + entryType +
+ ", resourceType=" + resourceType +
'}';
}
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java
index 025d12791d..797206bb7b 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java
@@ -37,7 +37,8 @@ public class DefaultSlotChainBuilder implements SlotChainBuilder {
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
- List sortedSlotList = SpiLoader.loadInstanceListSorted(ProcessorSlot.class);
+ // Note: the instances of ProcessorSlot should be different, since they are not stateless.
+ List sortedSlotList = SpiLoader.loadDifferentInstanceListSorted(ProcessorSlot.class);
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
index 978b169fbd..e712ff343c 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
@@ -36,6 +36,9 @@ public final class RuleConstant {
*/
public static final int DEGRADE_GRADE_EXCEPTION_COUNT = 2;
+ public static final int DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT = 5;
+ public static final int DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT = 5;
+
public static final int AUTHORITY_WHITE = 0;
public static final int AUTHORITY_BLACK = 1;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java
index f29c42d115..aee71ff543 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java
@@ -15,12 +15,6 @@
*/
package com.alibaba.csp.sentinel.slots.block.degrade;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.ClusterNode;
@@ -29,6 +23,12 @@
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
*
* Degrade is used when the resources are in an unstable state, these resources
@@ -55,8 +55,6 @@
*/
public class DegradeRule extends AbstractRule {
- private static final int RT_MAX_EXCEED_N = 5;
-
@SuppressWarnings("PMD.ThreadPoolCreationRule")
private static ScheduledExecutorService pool = Executors.newScheduledThreadPool(
Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true));
@@ -78,11 +76,23 @@ public DegradeRule(String resourceName) {
private int timeWindow;
/**
- * Degrade strategy (0: average RT, 1: exception ratio).
+ * Degrade strategy (0: average RT, 1: exception ratio, 2: exception count).
*/
private int grade = RuleConstant.DEGRADE_GRADE_RT;
- private final AtomicBoolean cut = new AtomicBoolean(false);
+ /**
+ * Minimum number of consecutive slow requests that can trigger RT circuit breaking.
+ *
+ * @since 1.7.0
+ */
+ private int rtSlowRequestAmount = RuleConstant.DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT;
+
+ /**
+ * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
+ *
+ * @since 1.7.0
+ */
+ private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
public int getGrade() {
return grade;
@@ -93,8 +103,6 @@ public DegradeRule setGrade(int grade) {
return this;
}
- private AtomicLong passCount = new AtomicLong(0);
-
public double getCount() {
return count;
}
@@ -104,51 +112,44 @@ public DegradeRule setCount(double count) {
return this;
}
- private boolean isCut() {
- return cut.get();
+ public int getTimeWindow() {
+ return timeWindow;
}
- private void setCut(boolean cut) {
- this.cut.set(cut);
+ public DegradeRule setTimeWindow(int timeWindow) {
+ this.timeWindow = timeWindow;
+ return this;
}
- public AtomicLong getPassCount() {
- return passCount;
+ public int getRtSlowRequestAmount() {
+ return rtSlowRequestAmount;
}
- public int getTimeWindow() {
- return timeWindow;
+ public DegradeRule setRtSlowRequestAmount(int rtSlowRequestAmount) {
+ this.rtSlowRequestAmount = rtSlowRequestAmount;
+ return this;
}
- public DegradeRule setTimeWindow(int timeWindow) {
- this.timeWindow = timeWindow;
+ public int getMinRequestAmount() {
+ return minRequestAmount;
+ }
+
+ public DegradeRule setMinRequestAmount(int minRequestAmount) {
+ this.minRequestAmount = minRequestAmount;
return this;
}
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof DegradeRule)) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
-
- DegradeRule that = (DegradeRule)o;
-
- if (count != that.count) {
- return false;
- }
- if (timeWindow != that.timeWindow) {
- return false;
- }
- if (grade != that.grade) {
- return false;
- }
- return true;
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+ if (!super.equals(o)) { return false; }
+ DegradeRule that = (DegradeRule) o;
+ return Double.compare(that.count, count) == 0 &&
+ timeWindow == that.timeWindow &&
+ grade == that.grade &&
+ rtSlowRequestAmount == that.rtSlowRequestAmount &&
+ minRequestAmount == that.minRequestAmount;
}
@Override
@@ -157,9 +158,29 @@ public int hashCode() {
result = 31 * result + new Double(count).hashCode();
result = 31 * result + timeWindow;
result = 31 * result + grade;
+ result = 31 * result + rtSlowRequestAmount;
+ result = 31 * result + minRequestAmount;
return result;
}
+ @Override
+ public String toString() {
+ return "DegradeRule{" +
+ "resource=" + getResource() +
+ ", grade=" + grade +
+ ", count=" + count +
+ ", limitApp=" + getLimitApp() +
+ ", timeWindow=" + timeWindow +
+ ", rtSlowRequestAmount=" + rtSlowRequestAmount +
+ ", minRequestAmount=" + minRequestAmount +
+ "}";
+ }
+
+ // Internal implementation (will be deprecated and moved outside).
+
+ private AtomicLong passCount = new AtomicLong(0);
+ private final AtomicBoolean cut = new AtomicBoolean(false);
+
@Override
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
if (cut.get()) {
@@ -179,20 +200,22 @@ public boolean passCheck(Context context, DefaultNode node, int acquireCount, Ob
}
// Sentinel will degrade the service only if count exceeds.
- if (passCount.incrementAndGet() < RT_MAX_EXCEED_N) {
+ if (passCount.incrementAndGet() < rtSlowRequestAmount) {
return true;
}
} else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
double exception = clusterNode.exceptionQps();
double success = clusterNode.successQps();
double total = clusterNode.totalQps();
- // if total qps less than RT_MAX_EXCEED_N, pass.
- if (total < RT_MAX_EXCEED_N) {
+ // If total amount is less than minRequestAmount, the request will pass.
+ if (total < minRequestAmount) {
return true;
}
+ // In the same aligned statistic time window,
+ // "success" (aka. completed count) = exception count + non-exception count (realSuccess)
double realSuccess = success - exception;
- if (realSuccess <= 0 && exception < RT_MAX_EXCEED_N) {
+ if (realSuccess <= 0 && exception < minRequestAmount) {
return true;
}
@@ -214,17 +237,6 @@ public boolean passCheck(Context context, DefaultNode node, int acquireCount, Ob
return false;
}
- @Override
- public String toString() {
- return "DegradeRule{" +
- "resource=" + getResource() +
- ", grade=" + grade +
- ", count=" + count +
- ", limitApp=" + getLimitApp() +
- ", timeWindow=" + timeWindow +
- "}";
- }
-
private static final class ResetTask implements Runnable {
private DegradeRule rule;
@@ -235,9 +247,8 @@ private static final class ResetTask implements Runnable {
@Override
public void run() {
- rule.getPassCount().set(0);
+ rule.passCount.set(0);
rule.cut.set(false);
}
}
}
-
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java
index 9eb7e58226..6cb09f8e97 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java
@@ -23,7 +23,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.node.DefaultNode;
@@ -211,16 +211,22 @@ public static boolean isValidRule(DegradeRule rule) {
if (!baseValid) {
return false;
}
- // Warn for RT mode that exceeds the {@code TIME_DROP_VALVE}.
- int maxAllowedRt = Constants.TIME_DROP_VALVE;
- if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT && rule.getCount() > maxAllowedRt) {
- RecordLog.warn(String.format("[DegradeRuleManager] WARN: setting large RT threshold (%.1f ms) in RT mode"
- + " will not take effect since it exceeds the max allowed value (%d ms)", rule.getCount(),
- maxAllowedRt));
+ int maxAllowedRt = SentinelConfig.statisticMaxRt();
+ if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT) {
+ if (rule.getRtSlowRequestAmount() <= 0) {
+ return false;
+ }
+ // Warn for RT mode that exceeds the {@code TIME_DROP_VALVE}.
+ if (rule.getCount() > maxAllowedRt) {
+ RecordLog.warn(String.format("[DegradeRuleManager] WARN: setting large RT threshold (%.1f ms)"
+ + " in RT mode will not take effect since it exceeds the max allowed value (%d ms)",
+ rule.getCount(), maxAllowedRt));
+ }
}
+
// Check exception ratio mode.
- if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO && rule.getCount() > 1) {
- return false;
+ if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
+ return rule.getCount() <= 1 && rule.getMinRequestAmount() > 0;
}
return true;
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
index 2c776d15fb..6a79e074d9 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
@@ -195,7 +195,7 @@ private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) {
if (!isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) {
return false;
}
- switch (rule.getStrategy()) {
+ switch (clusterConfig.getStrategy()) {
case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL:
return true;
default:
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
index 1cd5f138a0..f25289c9f6 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
@@ -80,7 +80,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode
synchronized (lock) {
if (clusterNode == null) {
// Create the cluster node.
- clusterNode = new ClusterNode();
+ clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
HashMap newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java
index 20bdee2465..9a6b683b53 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java
@@ -17,6 +17,7 @@
import java.util.Collection;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
@@ -67,7 +68,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode
context.getCurEntry().getOriginNode().addPassRequest(count);
}
- if (resourceWrapper.getType() == EntryType.IN) {
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseThreadNum();
Constants.ENTRY_NODE.addPassRequest(count);
@@ -84,7 +85,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode
context.getCurEntry().getOriginNode().increaseThreadNum();
}
- if (resourceWrapper.getType() == EntryType.IN) {
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseThreadNum();
}
@@ -102,7 +103,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode
context.getCurEntry().getOriginNode().increaseBlockQps(count);
}
- if (resourceWrapper.getType() == EntryType.IN) {
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
// Add count for global inbound entry node for global statistics.
Constants.ENTRY_NODE.increaseBlockQps(count);
}
@@ -123,7 +124,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode
context.getCurEntry().getOriginNode().increaseExceptionQps(count);
}
- if (resourceWrapper.getType() == EntryType.IN) {
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
Constants.ENTRY_NODE.increaseExceptionQps(count);
}
throw e;
@@ -135,10 +136,11 @@ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Ob
DefaultNode node = (DefaultNode)context.getCurNode();
if (context.getCurEntry().getError() == null) {
- // Calculate response time (max RT is TIME_DROP_VALVE).
+ // Calculate response time (max RT is statisticMaxRt from SentinelConfig).
long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime();
- if (rt > Constants.TIME_DROP_VALVE) {
- rt = Constants.TIME_DROP_VALVE;
+ int maxStatisticRt = SentinelConfig.statisticMaxRt();
+ if (rt > maxStatisticRt) {
+ rt = maxStatisticRt;
}
// Record response time and success count.
@@ -153,7 +155,7 @@ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Ob
context.getCurEntry().getOriginNode().decreaseThreadNum();
}
- if (resourceWrapper.getType() == EntryType.IN) {
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
Constants.ENTRY_NODE.addRtAndSuccess(rt, count);
Constants.ENTRY_NODE.decreaseThreadNum();
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LongAdder.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LongAdder.java
index beeaedcf67..e0e638a181 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LongAdder.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LongAdder.java
@@ -46,6 +46,7 @@ public class LongAdder extends Striped64 implements Serializable {
/**
* Version of plus for use in retryUpdate
*/
+ @Override
final long fn(long v, long x) { return v + x; }
/**
@@ -153,6 +154,7 @@ public long sumThenReset() {
*
* @return the String representation of the {@link #sum}
*/
+ @Override
public String toString() {
return Long.toString(sum());
}
@@ -162,6 +164,7 @@ public String toString() {
*
* @return the sum
*/
+ @Override
public long longValue() {
return sum();
}
@@ -170,6 +173,7 @@ public long longValue() {
* Returns the {@link #sum} as an {@code int} after a narrowing
* primitive conversion.
*/
+ @Override
public int intValue() {
return (int)sum();
}
@@ -178,6 +182,7 @@ public int intValue() {
* Returns the {@link #sum} as a {@code float}
* after a widening primitive conversion.
*/
+ @Override
public float floatValue() {
return (float)sum();
}
@@ -186,6 +191,7 @@ public float floatValue() {
* Returns the {@link #sum} as a {@code double} after a widening
* primitive conversion.
*/
+ @Override
public double doubleValue() {
return (double)sum();
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/Striped64.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/Striped64.java
index e4489e8ca6..cab42f0a42 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/Striped64.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/Striped64.java
@@ -136,6 +136,7 @@ static final class HashCode {
* The corresponding ThreadLocal class
*/
static final class ThreadHashCode extends ThreadLocal {
+ @Override
public HashCode initialValue() { return new HashCode(); }
}
@@ -330,6 +331,7 @@ private static sun.misc.Unsafe getUnsafe() {
return java.security.AccessController.doPrivileged
(new java.security
.PrivilegedExceptionAction() {
+ @Override
public sun.misc.Unsafe run() throws Exception {
java.lang.reflect.Field f = sun.misc
.Unsafe.class.getDeclaredField("theUnsafe");
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java
index ec3f2afaac..6c35e174b3 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java
@@ -15,7 +15,7 @@
*/
package com.alibaba.csp.sentinel.slots.statistic.data;
-import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
@@ -50,7 +50,7 @@ public MetricBucket reset(MetricBucket bucket) {
}
private void initMinRt() {
- this.minRt = Constants.TIME_DROP_VALVE;
+ this.minRt = SentinelConfig.statisticMaxRt();
}
/**
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
index 8647d99461..c21223a78c 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
@@ -18,13 +18,14 @@
import java.util.ArrayList;
import java.util.List;
-import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.node.metric.MetricNode;
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray;
+import com.alibaba.csp.sentinel.util.function.Predicate;
/**
* The basic metric class in Sentinel using a {@link BucketLeapArray} internal.
@@ -140,7 +141,7 @@ public long rt() {
@Override
public long minRt() {
data.currentWindow();
- long rt = Constants.TIME_DROP_VALVE;
+ long rt = SentinelConfig.statisticMaxRt();
List list = data.values();
for (MetricBucket window : list) {
if (window.minRt() < rt) {
@@ -153,33 +154,56 @@ public long minRt() {
@Override
public List details() {
- List details = new ArrayList();
+ List details = new ArrayList<>();
data.currentWindow();
List> list = data.list();
for (WindowWrap window : list) {
if (window == null) {
continue;
}
- MetricNode node = new MetricNode();
- node.setBlockQps(window.value().block());
- node.setExceptionQps(window.value().exception());
- node.setPassQps(window.value().pass());
- long successQps = window.value().success();
- node.setSuccessQps(successQps);
- if (successQps != 0) {
- node.setRt(window.value().rt() / successQps);
- } else {
- node.setRt(window.value().rt());
+
+ details.add(fromBucket(window));
+ }
+
+ return details;
+ }
+
+ @Override
+ public List detailsOnCondition(Predicate timePredicate) {
+ List details = new ArrayList<>();
+ data.currentWindow();
+ List> list = data.list();
+ for (WindowWrap window : list) {
+ if (window == null) {
+ continue;
+ }
+ if (timePredicate != null && !timePredicate.test(window.windowStart())) {
+ continue;
}
- node.setTimestamp(window.windowStart());
- node.setOccupiedPassQps(window.value().occupiedPass());
- details.add(node);
+ details.add(fromBucket(window));
}
return details;
}
+ private MetricNode fromBucket(WindowWrap wrap) {
+ MetricNode node = new MetricNode();
+ node.setBlockQps(wrap.value().block());
+ node.setExceptionQps(wrap.value().exception());
+ node.setPassQps(wrap.value().pass());
+ long successQps = wrap.value().success();
+ node.setSuccessQps(successQps);
+ if (successQps != 0) {
+ node.setRt(wrap.value().rt() / successQps);
+ } else {
+ node.setRt(wrap.value().rt());
+ }
+ node.setTimestamp(wrap.windowStart());
+ node.setOccupiedPassQps(wrap.value().occupiedPass());
+ return node;
+ }
+
@Override
public MetricBucket[] windows() {
data.currentWindow();
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
index f6a73f5f9c..b79cd881bf 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
@@ -19,6 +19,7 @@
import com.alibaba.csp.sentinel.node.metric.MetricNode;
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+import com.alibaba.csp.sentinel.util.function.Predicate;
/**
* Represents a basic structure recording invocation metrics of protected resources.
@@ -84,6 +85,15 @@ public interface Metric extends DebugSupport {
*/
List details();
+ /**
+ * Generate aggregated metric items that satisfies the time predicate.
+ *
+ * @param timePredicate time predicate
+ * @return aggregated metric items
+ * @since 1.7.0
+ */
+ List detailsOnCondition(Predicate timePredicate);
+
/**
* Get the raw window array.
*
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java
index 7a6091fe57..d860344e2f 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java
@@ -63,7 +63,7 @@
* @author jialiang.linjl
* @author leyou
*/
-public class SystemRuleManager {
+public final class SystemRuleManager {
private static volatile double highestSystemLoad = Double.MAX_VALUE;
/**
@@ -95,7 +95,7 @@ public class SystemRuleManager {
static {
checkSystemStatus.set(false);
statusListener = new SystemStatusListener();
- scheduler.scheduleAtFixedRate(statusListener, 5, 1, TimeUnit.SECONDS);
+ scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS);
currentProperty.addListener(listener);
}
@@ -107,6 +107,7 @@ public class SystemRuleManager {
*/
public static void register2Property(SentinelProperty> property) {
synchronized (listener) {
+ RecordLog.info("[SystemRuleManager] Registering new property to system rule manager");
currentProperty.removeListener(listener);
property.addListener(listener);
currentProperty = property;
@@ -167,26 +168,22 @@ public static List getRules() {
return result;
}
- public static double getQps() {
+ public static double getInboundQpsThreshold() {
return qps;
}
- public static void setQps(double qps) {
- SystemRuleManager.qps = qps;
- }
-
- public static long getMaxRt() {
+ public static long getRtThreshold() {
return maxRt;
}
- public static long getMaxThread() {
+ public static long getMaxThreadThreshold() {
return maxThread;
}
static class SystemPropertyListener extends SimplePropertyListener> {
@Override
- public void configUpdate(List rules) {
+ public synchronized void configUpdate(List rules) {
restoreSetting();
// systemRules = rules;
if (rules != null && rules.size() >= 1) {
@@ -234,14 +231,10 @@ public static Boolean getCheckSystemStatus() {
return checkSystemStatus.get();
}
- public static double getHighestSystemLoad() {
+ public static double getSystemLoadThreshold() {
return highestSystemLoad;
}
- public static void setHighestSystemLoad(double highestSystemLoad) {
- SystemRuleManager.highestSystemLoad = highestSystemLoad;
- }
-
public static double getCpuUsageThreshold() {
return highestCpuUsage;
}
@@ -257,9 +250,14 @@ public static void loadSystemConf(SystemRule rule) {
}
if (rule.getHighestCpuUsage() >= 0) {
- highestCpuUsage = Math.min(highestCpuUsage, rule.getHighestCpuUsage());
- highestCpuUsageIsSet = true;
- checkStatus = true;
+ if (rule.getHighestCpuUsage() > 1) {
+ RecordLog.warn(String.format("[SystemRuleManager] Ignoring invalid SystemRule: "
+ + "highestCpuUsage %.3f > 1", rule.getHighestCpuUsage()));
+ } else {
+ highestCpuUsage = Math.min(highestCpuUsage, rule.getHighestCpuUsage());
+ highestCpuUsageIsSet = true;
+ checkStatus = true;
+ }
}
if (rule.getAvgRt() >= 0) {
@@ -290,13 +288,16 @@ public static void loadSystemConf(SystemRule rule) {
* @throws BlockException when any system rule's threshold is exceeded.
*/
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
+ if (resourceWrapper == null) {
+ return;
+ }
// Ensure the checking switch is on.
if (!checkSystemStatus.get()) {
return;
}
// for inbound traffic only
- if (resourceWrapper.getType() != EntryType.IN) {
+ if (resourceWrapper.getEntryType() != EntryType.IN) {
return;
}
@@ -326,9 +327,7 @@ public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockExce
// cpu usage
if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
- if (!checkBbr(currentThread)) {
- throw new SystemBlockException(resourceWrapper.getName(), "cpu");
- }
+ throw new SystemBlockException(resourceWrapper.getName(), "cpu");
}
}
@@ -347,4 +346,4 @@ public static double getCurrentSystemAvgLoad() {
public static double getCurrentCpuUsage() {
return statusListener.getCpuUsage();
}
-}
\ No newline at end of file
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java
index e02f0c858b..2310160a16 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java
@@ -46,32 +46,35 @@ public void run() {
try {
OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
currentLoad = osBean.getSystemLoadAverage();
- /**
+ /*
* Java Doc copied from {@link OperatingSystemMXBean#getSystemCpuLoad()}:
* Returns the "recent cpu usage" for the whole system. This value is a double in the [0.0,1.0] interval.
* A value of 0.0 means that all CPUs were idle during the recent period of time observed, while a value
* of 1.0 means that all CPUs were actively running 100% of the time during the recent period being
- * observed. All values betweens 0.0 and 1.0 are possible depending of the activities going on in the
+ * observed. All values between 0.0 and 1.0 are possible depending of the activities going on in the
* system. If the system recent cpu usage is not available, the method returns a negative value.
*/
currentCpuUsage = osBean.getSystemCpuLoad();
- StringBuilder sb = new StringBuilder();
- if (currentLoad > SystemRuleManager.getHighestSystemLoad()) {
- sb.append("load:").append(currentLoad).append(";");
- sb.append("cpu:").append(currentCpuUsage).append(";");
- sb.append("qps:").append(Constants.ENTRY_NODE.passQps()).append(";");
- sb.append("rt:").append(Constants.ENTRY_NODE.avgRt()).append(";");
- sb.append("thread:").append(Constants.ENTRY_NODE.curThreadNum()).append(";");
- sb.append("success:").append(Constants.ENTRY_NODE.successQps()).append(";");
- sb.append("minRt:").append(Constants.ENTRY_NODE.minRt()).append(";");
- sb.append("maxSuccess:").append(Constants.ENTRY_NODE.maxSuccessQps()).append(";");
- RecordLog.info(sb.toString());
+ if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) {
+ writeSystemStatusLog();
}
-
} catch (Throwable e) {
- RecordLog.info("could not get system error ", e);
+ RecordLog.warn("[SystemStatusListener] Failed to get system metrics from JMX", e);
}
}
+ private void writeSystemStatusLog() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Load exceeds the threshold: ");
+ sb.append("load:").append(String.format("%.4f", currentLoad)).append("; ");
+ sb.append("cpuUsage:").append(String.format("%.4f", currentCpuUsage)).append("; ");
+ sb.append("qps:").append(String.format("%.4f", Constants.ENTRY_NODE.passQps())).append("; ");
+ sb.append("rt:").append(String.format("%.4f", Constants.ENTRY_NODE.avgRt())).append("; ");
+ sb.append("thread:").append(Constants.ENTRY_NODE.curThreadNum()).append("; ");
+ sb.append("success:").append(String.format("%.4f", Constants.ENTRY_NODE.successQps())).append("; ");
+ sb.append("minRt:").append(String.format("%.2f", Constants.ENTRY_NODE.minRt())).append("; ");
+ sb.append("maxSuccess:").append(String.format("%.2f", Constants.ENTRY_NODE.maxSuccessQps())).append("; ");
+ RecordLog.info(sb.toString());
+ }
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/ServiceLoaderUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/ServiceLoaderUtil.java
new file mode 100644
index 0000000000..f7fe6fde03
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/spi/ServiceLoaderUtil.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.spi;
+
+import java.util.ServiceLoader;
+
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+
+/**
+ * @author Eric Zhao
+ * @since 1.7.0
+ */
+public final class ServiceLoaderUtil {
+
+ private static final String CLASSLOADER_DEFAULT = "default";
+ private static final String CLASSLOADER_CONTEXT = "context";
+
+ public static ServiceLoader getServiceLoader(Class clazz) {
+ if (shouldUseContextClassloader()) {
+ return ServiceLoader.load(clazz);
+ } else {
+ return ServiceLoader.load(clazz, clazz.getClassLoader());
+ }
+ }
+
+ public static boolean shouldUseContextClassloader() {
+ String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER);
+ return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf);
+ }
+
+ private ServiceLoaderUtil() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java
index 0cde3e85d2..5cfa766ee2 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java
@@ -15,10 +15,10 @@
*/
package com.alibaba.csp.sentinel.util;
-import java.io.File;
-
import com.alibaba.csp.sentinel.log.RecordLog;
+import java.io.File;
+
/**
* Util class for getting application name. This class uses the flowing order to get app's name:
*
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
index 4c67b738ff..545bcc4d28 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
@@ -15,8 +15,12 @@
*/
package com.alibaba.csp.sentinel.util;
+import java.util.Collection;
+
/**
* Util class for checking arguments.
+ *
+ * @author Eric Zhao
*/
public class AssertUtil {
@@ -28,6 +32,12 @@ public static void notEmpty(String string, String message) {
}
}
+ public static void assertNotEmpty(Collection> collection, String message) {
+ if (collection == null || collection.isEmpty()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
public static void assertNotBlank(String string, String message) {
if (StringUtil.isBlank(string)) {
throw new IllegalArgumentException(message);
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/ConfigUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/ConfigUtil.java
new file mode 100644
index 0000000000..a5182b363e
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/ConfigUtil.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ *
+ * Util class for loading configuration from file or command arguments.
+ *
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public final class ConfigUtil {
+
+ public static final String CLASSPATH_FILE_FLAG = "classpath:";
+
+ /**
+ *
Load the properties from provided file.
+ *
Currently it supports reading from classpath file or local file.
+ *
+ * @param fileName valid file path
+ * @return the retrieved properties from the file; null if the file not exist
+ */
+ public static Properties loadProperties(String fileName) {
+ if (StringUtil.isNotBlank(fileName)) {
+ if (absolutePathStart(fileName)) {
+ return loadPropertiesFromAbsoluteFile(fileName);
+ } else if (fileName.startsWith(CLASSPATH_FILE_FLAG)) {
+ return loadPropertiesFromClasspathFile(fileName);
+ } else {
+ return loadPropertiesFromRelativeFile(fileName);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ private static Properties loadPropertiesFromAbsoluteFile(String fileName) {
+ Properties properties = null;
+ try {
+
+ File file = new File(fileName);
+ if (!file.exists()) {
+ return null;
+ }
+
+ try (BufferedReader bufferedReader =
+ new BufferedReader(new InputStreamReader(new FileInputStream(file), getCharset()))) {
+ properties = new Properties();
+ properties.load(bufferedReader);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return properties;
+ }
+
+ private static boolean absolutePathStart(String path) {
+ File[] files = File.listRoots();
+ for (File file : files) {
+ if (path.startsWith(file.getPath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private static Properties loadPropertiesFromClasspathFile(String fileName) {
+ fileName = fileName.substring(CLASSPATH_FILE_FLAG.length()).trim();
+
+ List list = new ArrayList<>();
+ try {
+ Enumeration urls = getClassLoader().getResources(fileName);
+ list = new ArrayList<>();
+ while (urls.hasMoreElements()) {
+ list.add(urls.nextElement());
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ if (list.isEmpty()) {
+ return null;
+ }
+
+ Properties properties = new Properties();
+ for (URL url : list) {
+ try (BufferedReader bufferedReader =
+ new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) {
+ Properties p = new Properties();
+ p.load(bufferedReader);
+ properties.putAll(p);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ return properties;
+ }
+
+ private static Properties loadPropertiesFromRelativeFile(String fileName) {
+ String userDir = System.getProperty("user.dir");
+ String realFilePath = addSeparator(userDir) + fileName;
+ return loadPropertiesFromAbsoluteFile(realFilePath);
+ }
+
+ private static ClassLoader getClassLoader() {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ if (classLoader == null) {
+ classLoader = ConfigUtil.class.getClassLoader();
+ }
+ return classLoader;
+ }
+
+ private static Charset getCharset() {
+ // avoid static loop dependencies: SentinelConfig -> SentinelConfigLoader -> ConfigUtil -> SentinelConfig
+ // so not use SentinelConfig.charset()
+ return Charset.forName(System.getProperty("csp.sentinel.charset", StandardCharsets.UTF_8.name()));
+ }
+
+ public static String addSeparator(String dir) {
+ if (!dir.endsWith(File.separator)) {
+ dir += File.separator;
+ }
+ return dir;
+ }
+
+ private ConfigUtil() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
index af89ea7f83..a632924461 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
@@ -23,6 +23,7 @@
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.spi.ServiceLoaderUtil;
import com.alibaba.csp.sentinel.spi.SpiOrder;
/**
@@ -34,12 +35,13 @@ public final class SpiLoader {
private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap();
public static T loadFirstInstance(Class clazz) {
+ AssertUtil.notNull(clazz, "SPI class cannot be null");
try {
String key = clazz.getName();
// Not thread-safe, as it's expected to be resolved in a thread-safe context.
ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
if (serviceLoader == null) {
- serviceLoader = ServiceLoader.load(clazz);
+ serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
SERVICE_LOADER_MAP.put(key, serviceLoader);
}
@@ -56,6 +58,41 @@ public static T loadFirstInstance(Class clazz) {
}
}
+ /**
+ * Load the first-found specific SPI instance (excluding provided default SPI class).
+ * If no other SPI implementation found, then create a default SPI instance.
+ *
+ * @param clazz class of the SPI interface
+ * @param defaultClass class of the default SPI implementation (if no other implementation found)
+ * @param SPI type
+ * @return the first specific SPI instance if exists, or else the default SPI instance
+ * @since 1.7.0
+ */
+ public static T loadFirstInstanceOrDefault(Class clazz, Class extends T> defaultClass) {
+ AssertUtil.notNull(clazz, "SPI class cannot be null");
+ AssertUtil.notNull(defaultClass, "default SPI class cannot be null");
+ try {
+ String key = clazz.getName();
+ // Not thread-safe, as it's expected to be resolved in a thread-safe context.
+ ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
+ if (serviceLoader == null) {
+ serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
+ SERVICE_LOADER_MAP.put(key, serviceLoader);
+ }
+
+ for (T instance : serviceLoader) {
+ if (instance.getClass() != defaultClass) {
+ return instance;
+ }
+ }
+ return defaultClass.newInstance();
+ } catch (Throwable t) {
+ RecordLog.warn("[SpiLoader] ERROR: loadFirstInstanceOrDefault failed", t);
+ t.printStackTrace();
+ return null;
+ }
+ }
+
/**
* Load the SPI instance with highest priority.
*
@@ -70,7 +107,7 @@ public static T loadHighestPriorityInstance(Class clazz) {
// Not thread-safe, as it's expected to be resolved in a thread-safe context.
ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
if (serviceLoader == null) {
- serviceLoader = ServiceLoader.load(clazz);
+ serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
SERVICE_LOADER_MAP.put(key, serviceLoader);
}
@@ -92,7 +129,6 @@ public static T loadHighestPriorityInstance(Class clazz) {
}
/**
- * Load and sorted SPI instance list.
* Load the SPI instance list for provided SPI interface.
*
* @param clazz class of the SPI
@@ -106,7 +142,7 @@ public static List loadInstanceList(Class clazz) {
// Not thread-safe, as it's expected to be resolved in a thread-safe context.
ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
if (serviceLoader == null) {
- serviceLoader = ServiceLoader.load(clazz);
+ serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
SERVICE_LOADER_MAP.put(key, serviceLoader);
}
@@ -125,8 +161,6 @@ public static List loadInstanceList(Class clazz) {
/**
* Load the sorted SPI instance list for provided SPI interface.
*
- * Note: each call return new instances.
- *
* @param clazz class of the SPI
* @param SPI type
* @return sorted SPI instance list
@@ -134,8 +168,13 @@ public static List loadInstanceList(Class clazz) {
*/
public static List loadInstanceListSorted(Class clazz) {
try {
- // Call ServiceLoader.load(clazz) to get new instance
- ServiceLoader serviceLoader = ServiceLoader.load(clazz);
+ String key = clazz.getName();
+ // Not thread-safe, as it's expected to be resolved in a thread-safe context.
+ ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
+ if (serviceLoader == null) {
+ serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
+ SERVICE_LOADER_MAP.put(key, serviceLoader);
+ }
List> orderWrappers = new ArrayList<>();
for (T spi : serviceLoader) {
@@ -157,6 +196,41 @@ public static List loadInstanceListSorted(Class clazz) {
}
}
+ /**
+ * Load the sorted SPI instance list for provided SPI interface.
+ *
+ * @param clazz class of the SPI
+ * @param SPI type
+ * @return sorted SPI instance list, and instances are different from prev call
+ * @since 1.7.2
+ */
+ public static List loadDifferentInstanceListSorted(Class clazz) {
+ try {
+ String key = clazz.getName();
+
+ // To make sure load new instance
+ ServiceLoader serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
+
+ List> orderWrappers = new ArrayList<>();
+ for (T spi : serviceLoader) {
+ int order = SpiOrderResolver.resolveOrder(spi);
+ // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here.
+ SpiOrderResolver.insertSorted(orderWrappers, spi, order);
+ RecordLog.info("[SpiLoader] Found {0} SPI: {1} with order " + order, clazz.getSimpleName(),
+ spi.getClass().getCanonicalName());
+ }
+ List list = new ArrayList<>();
+ for (int i = 0; i < orderWrappers.size(); i++) {
+ list.add(i, orderWrappers.get(i).spi);
+ }
+ return list;
+ } catch (Throwable t) {
+ RecordLog.warn("[SpiLoader] ERROR: loadInstanceListSorted failed", t);
+ t.printStackTrace();
+ return new ArrayList<>();
+ }
+ }
+
private static class SpiOrderResolver {
private static void insertSorted(List> list, T spi, int order) {
int idx = 0;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Consumer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Consumer.java
new file mode 100644
index 0000000000..310fb0c509
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Consumer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.util.function;
+
+/**
+ * Consumer interface from JDK 8.
+ */
+public interface Consumer {
+
+ /**
+ * Performs this operation on the given argument.
+ *
+ * @param t the input argument
+ */
+ void accept(T t);
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConstantsTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConstantsTest.java
deleted file mode 100644
index 03084270b1..0000000000
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConstantsTest.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.alibaba.csp.sentinel;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Test cases for {@link Constants}.
- *
- * @author cdfive
- */
-public class ConstantsTest {
-
- @Test
- public void testDefaultTimeDropValue() {
- assertEquals(4900, Constants.TIME_DROP_VALVE);
- }
-
-// add JVM parameter
-// -Dcsp.sentinel.statistic.max.rt=10000
-// @Test
- public void testCustomTimeDropValue() {
- assertEquals(10000, Constants.TIME_DROP_VALVE);
- }
-}
\ No newline at end of file
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java
index 8c203f00cb..1d910ee76a 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/CtSphTest.java
@@ -41,7 +41,7 @@ private void testCustomContextEntryWithFullContextSize(String resourceName, bool
Entry entry = null;
try {
if (async) {
- entry = ctSph.asyncEntry(resourceName, resourceWrapper.getType(), 1);
+ entry = ctSph.asyncEntry(resourceName, resourceWrapper.getEntryType(), 1);
} else {
entry = ctSph.entry(resourceWrapper, 1);
}
@@ -80,7 +80,7 @@ private void testDefaultContextEntryWithFullContextSize(String resourceName, boo
if (!async) {
entry = ctSph.entry(resourceWrapper, 1);
} else {
- entry = ctSph.asyncEntry(resourceName, resourceWrapper.getType(), 1);
+ entry = ctSph.asyncEntry(resourceName, resourceWrapper.getEntryType(), 1);
Context asyncContext = ((AsyncEntry)entry).getAsyncContext();
assertTrue(ContextUtil.isDefaultContext(asyncContext));
assertTrue(asyncContext.isAsync());
@@ -127,7 +127,7 @@ public void testEntryAndAsyncEntryWhenSwitchOff() {
AsyncEntry asyncEntry = null;
try {
entry = ctSph.entry(resourceWrapperA, 1);
- asyncEntry = ctSph.asyncEntry(resourceNameB, resourceWrapperB.getType(), 1);
+ asyncEntry = ctSph.asyncEntry(resourceNameB, resourceWrapperB.getEntryType(), 1);
} catch (BlockException ex) {
fail("Unexpected blocked: " + ex.getClass().getCanonicalName());
} finally {
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java
index e2e4bd995f..50f9b63fa1 100755
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java
@@ -15,14 +15,14 @@
*/
package com.alibaba.csp.sentinel;
-import java.io.File;
-
import com.alibaba.csp.sentinel.log.LogBase;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.PidUtil;
-
+import org.junit.Assert;
import org.junit.Test;
+import java.io.File;
+
import static org.junit.Assert.assertTrue;
/**
@@ -44,21 +44,27 @@ public void testLogRolling() {
}
}
- @Test
+ //Change LogBase It is not not work when integration Testing
+ //Because LogBase.LOG_DIR can be just static init for once and it will not be changed
+ //@Test
public void testChangeLogBase() {
+
String userHome = System.getProperty("user.home");
String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis();
System.setProperty(LogBase.LOG_DIR, newLogBase);
RecordLog.info("testChangeLogBase");
String logFileName = RecordLog.getLogBaseDir();
+ Assert.assertTrue(newLogBase.equals(logFileName));
File[] files = new File(logFileName).listFiles();
assertTrue(files != null && files.length > 0);
+ deleteLogDir(new File(newLogBase));
+
+
}
@Test
public void testLogBaseDir() {
- RecordLog.info("testLogBaseDir");
assertTrue(RecordLog.getLogBaseDir().startsWith(System.getProperty("user.home")));
}
@@ -88,4 +94,17 @@ public void testLogNameUsePid() {
}
}
+ private void deleteLogDir(File logDirFile) {
+ if (logDirFile != null && logDirFile.isDirectory()) {
+ if (logDirFile.listFiles() != null) {
+ for (File file : logDirFile.listFiles()) {
+ file.delete();
+ }
+ }
+ logDirFile.delete();
+ }
+ }
+
+
+
}
\ No newline at end of file
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphOTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphOTest.java
index 7f8787a512..4233a70241 100755
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphOTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphOTest.java
@@ -63,7 +63,7 @@ public void testStringEntryCount() {
try {
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.OUT);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.OUT);
} finally {
SphO.exit(2);
}
@@ -78,7 +78,7 @@ public void testMethodEntryCount() throws NoSuchMethodException, SecurityExcepti
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(),
"com.alibaba.csp.sentinel.SphOTest:testMethodEntryCount()"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.OUT);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.OUT);
} finally {
SphO.exit(2);
}
@@ -91,7 +91,7 @@ public void testStringEntryType() {
try {
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit();
}
@@ -106,7 +106,7 @@ public void testMethodEntryType() throws NoSuchMethodException, SecurityExceptio
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(),
"com.alibaba.csp.sentinel.SphOTest:testMethodEntryType()"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit();
}
@@ -119,7 +119,7 @@ public void testStringEntryTypeCount() {
try {
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit(2);
}
@@ -134,7 +134,7 @@ public void testMethodEntryTypeCount() throws NoSuchMethodException, SecurityExc
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(),
"com.alibaba.csp.sentinel.SphOTest:testMethodEntryTypeCount()"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit(2);
}
@@ -147,7 +147,7 @@ public void testStringEntryAll() {
try {
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(), "resourceName"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit(2, "hello1", "hello2");
}
@@ -162,7 +162,7 @@ public void testMethodEntryAll() throws NoSuchMethodException, SecurityException
assertTrue(StringUtil.equalsIgnoreCase(
ContextUtil.getContext().getCurEntry().getResourceWrapper().getName(),
"com.alibaba.csp.sentinel.SphOTest:testMethodEntryAll()"));
- assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getType(), EntryType.IN);
+ assertSame(ContextUtil.getContext().getCurEntry().getResourceWrapper().getEntryType(), EntryType.IN);
} finally {
SphO.exit(2, "hello1", "hello2");
}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
index a1a58a1a96..e546f87c7f 100755
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/SphUTest.java
@@ -38,7 +38,7 @@ public void testStringEntryNormal() throws BlockException {
assertNotNull(e);
assertEquals(e.resourceWrapper.getName(), "resourceName");
- assertEquals(e.resourceWrapper.getType(), EntryType.OUT);
+ assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT);
assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME);
e.exit();
@@ -53,7 +53,7 @@ public void testMethodEntryNormal() throws BlockException, NoSuchMethodException
assertTrue(StringUtil
.equalsIgnoreCase(e.resourceWrapper.getName(),
"com.alibaba.csp.sentinel.SphUTest:testMethodEntryNormal()"));
- assertEquals(e.resourceWrapper.getType(), EntryType.OUT);
+ assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT);
assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME);
e.exit();
@@ -78,7 +78,7 @@ public void testStringEntryCount() throws BlockException {
assertNotNull(e);
assertEquals("resourceName", e.resourceWrapper.getName());
- assertEquals(e.resourceWrapper.getType(), EntryType.OUT);
+ assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT);
assertEquals(ContextUtil.getContext().getName(), Constants.CONTEXT_DEFAULT_NAME);
e.exit(2);
@@ -93,7 +93,7 @@ public void testMethodEntryCount() throws BlockException, NoSuchMethodException,
assertTrue(StringUtil
.equalsIgnoreCase(e.resourceWrapper.getName(),
"com.alibaba.csp.sentinel.SphUTest:testMethodEntryNormal()"));
- assertEquals(e.resourceWrapper.getType(), EntryType.OUT);
+ assertEquals(e.resourceWrapper.getEntryType(), EntryType.OUT);
e.exit(2);
}
@@ -102,7 +102,7 @@ public void testMethodEntryCount() throws BlockException, NoSuchMethodException,
public void testStringEntryType() throws BlockException {
Entry e = SphU.entry("resourceName", EntryType.IN);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit();
}
@@ -112,7 +112,7 @@ public void testMethodEntryType() throws BlockException, NoSuchMethodException,
Method method = SphUTest.class.getMethod("testMethodEntryNormal");
Entry e = SphU.entry(method, EntryType.IN);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit();
}
@@ -121,7 +121,7 @@ public void testMethodEntryType() throws BlockException, NoSuchMethodException,
public void testStringEntryCountType() throws BlockException {
Entry e = SphU.entry("resourceName", EntryType.IN, 2);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit(2);
}
@@ -131,7 +131,7 @@ public void testMethodEntryCountType() throws BlockException, NoSuchMethodExcept
Method method = SphUTest.class.getMethod("testMethodEntryNormal");
Entry e = SphU.entry(method, EntryType.IN, 2);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit();
}
@@ -141,7 +141,7 @@ public void testStringEntryAll() throws BlockException {
final String arg0 = "foo";
final String arg1 = "baz";
Entry e = SphU.entry("resourceName", EntryType.IN, 2, arg0, arg1);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit(2, arg0, arg1);
}
@@ -153,7 +153,7 @@ public void testMethodEntryAll() throws BlockException, NoSuchMethodException, S
Method method = SphUTest.class.getMethod("testMethodEntryNormal");
Entry e = SphU.entry(method, EntryType.IN, 2, arg0, arg1);
- assertSame(e.resourceWrapper.getType(), EntryType.IN);
+ assertSame(e.resourceWrapper.getEntryType(), EntryType.IN);
e.exit(2, arg0, arg1);
}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java
index 51ecb9a7a4..3108a08dcd 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java
@@ -1,7 +1,15 @@
package com.alibaba.csp.sentinel.config;
+import org.junit.Assert;
import org.junit.Test;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import static com.alibaba.csp.sentinel.config.SentinelConfig.*;
+import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
import static org.junit.Assert.assertEquals;
/**
@@ -20,7 +28,7 @@ public void testDefaultConfig() {
assertEquals(SentinelConfig.DEFAULT_STATISTIC_MAX_RT, SentinelConfig.statisticMaxRt());
}
-// add JVM parameter
+ // add JVM parameter
// -Dcsp.sentinel.charset=gbk
// -Dcsp.sentinel.metric.file.single.size=104857600
// -Dcsp.sentinel.metric.file.total.count=10
@@ -58,4 +66,51 @@ public void testColdFactoryLargerThanOne() {
SentinelConfig.setConfig(SentinelConfig.COLD_FACTOR, "4");
assertEquals(4, SentinelConfig.coldFactor());
}
+
+
+ //add Jvm parameter
+ //-Dcsp.sentinel.config.file=sentinel-propertiesTest.properties
+ //-Dcsp.sentinel.flow.cold.factor=5
+ //-Dcsp.sentinel.statistic.max.rt=1000
+ //@Test
+ public void testLoadProperties() throws IOException {
+
+ File file = null;
+ String fileName = "sentinel-propertiesTest.properties";
+ try {
+ file = new File(addSeparator(System.getProperty("user.dir")) + "target/classes/" + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ BufferedWriter out = new BufferedWriter(new FileWriter(file));
+ out.write(buildPropertyStr(CHARSET, "utf-8"));
+ out.write("\n");
+ out.write(buildPropertyStr(SINGLE_METRIC_FILE_SIZE, "1000"));
+ out.write("\n");
+ out.write(buildPropertyStr(TOTAL_METRIC_FILE_COUNT, "20"));
+ out.write("\n");
+ out.write(buildPropertyStr(COLD_FACTOR, "123"));
+ out.write("\n");
+ out.write(buildPropertyStr(STATISTIC_MAX_RT, "6000"));
+ out.flush();
+ out.close();
+
+ Assert.assertTrue(SentinelConfig.getConfig(CHARSET).equals("utf-8"));
+ Assert.assertTrue(SentinelConfig.getConfig(SINGLE_METRIC_FILE_SIZE).equals("1000"));
+ Assert.assertTrue(SentinelConfig.getConfig(TOTAL_METRIC_FILE_COUNT).equals("20"));
+ Assert.assertTrue(SentinelConfig.getConfig(COLD_FACTOR).equals("5"));
+ Assert.assertTrue(SentinelConfig.getConfig(STATISTIC_MAX_RT).equals("1000"));
+
+ } finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+
+ }
+
+ private String buildPropertyStr(String key, String value) {
+ return key + "=" + value;
+ }
}
\ No newline at end of file
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java
new file mode 100644
index 0000000000..41c8e0329b
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 1999-2019 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.eagleeye;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class EagleEyeCoreUtilsTest {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testIsBlank() {
+ Assert.assertTrue(EagleEyeCoreUtils.isBlank(""));
+ Assert.assertTrue(EagleEyeCoreUtils.isBlank(" "));
+ Assert.assertTrue(EagleEyeCoreUtils.isBlank(null));
+
+ Assert.assertFalse(EagleEyeCoreUtils.isBlank("foo"));
+ }
+
+ @Test
+ public void testCheckNotNullEmpty() {
+ Assert.assertEquals("foo",
+ EagleEyeCoreUtils.checkNotNullEmpty("foo", "bar"));
+ }
+
+ @Test
+ public void testCheckNotNullEmptyInputEmptyStringThrowsException() {
+ thrown.expect(IllegalArgumentException.class);
+ EagleEyeCoreUtils.checkNotNullEmpty("", "bar");
+ // Method is not expected to return due to exception thrown
+ }
+
+ @Test
+ public void testCheckNotNullEmptyInputSpaceThrowsException() {
+ thrown.expect(IllegalArgumentException.class);
+ EagleEyeCoreUtils.checkNotNullEmpty(" ", "bar");
+ // Method is not expected to return due to exception thrown
+ }
+
+ @Test
+ public void testCheckNotNullEmptyInputNullThrowsException() {
+ thrown.expect(IllegalArgumentException.class);
+ EagleEyeCoreUtils.checkNotNullEmpty(null, "bar");
+ // Method is not expected to return due to exception thrown
+ }
+
+ @Test
+ public void testCheckNotNull() {
+ Assert.assertEquals("", EagleEyeCoreUtils.checkNotNull("", "bar"));
+ Assert.assertEquals(" ", EagleEyeCoreUtils.checkNotNull(" ", "bar"));
+ Assert.assertEquals("foo",
+ EagleEyeCoreUtils.checkNotNull("foo", "bar"));
+ }
+
+ @Test
+ public void testCheckNotNullThrowsException() {
+ thrown.expect(IllegalArgumentException.class);
+ EagleEyeCoreUtils.checkNotNull(null, "bar");
+ // Method is not expected to return due to exception thrown
+ }
+
+ @Test
+ public void testDefaultIfNull() {
+ Assert.assertEquals("", EagleEyeCoreUtils.defaultIfNull("", "bar"));
+ Assert.assertEquals(" ", EagleEyeCoreUtils.defaultIfNull(" ", "bar"));
+ Assert.assertEquals("bar",
+ EagleEyeCoreUtils.defaultIfNull(null, "bar"));
+ Assert.assertEquals("foo",
+ EagleEyeCoreUtils.defaultIfNull("foo", "bar"));
+ }
+
+ @Test
+ public void testIsNotBlank() {
+ Assert.assertFalse(EagleEyeCoreUtils.isNotBlank(""));
+ Assert.assertFalse(EagleEyeCoreUtils.isNotBlank(" "));
+ Assert.assertFalse(EagleEyeCoreUtils.isNotBlank(null));
+
+ Assert.assertTrue(EagleEyeCoreUtils.isNotBlank("foo"));
+ }
+
+ @Test
+ public void testIsNotEmpty() {
+ Assert.assertFalse(EagleEyeCoreUtils.isNotEmpty(""));
+ Assert.assertFalse(EagleEyeCoreUtils.isNotEmpty(null));
+
+ Assert.assertTrue(EagleEyeCoreUtils.isNotEmpty(" "));
+ Assert.assertTrue(EagleEyeCoreUtils.isNotEmpty("foo"));
+ }
+
+ @Test
+ public void testTrim() {
+ Assert.assertNull(EagleEyeCoreUtils.trim(null));
+
+ Assert.assertEquals("", EagleEyeCoreUtils.trim(""));
+ Assert.assertEquals("", EagleEyeCoreUtils.trim(" "));
+ Assert.assertEquals("foo", EagleEyeCoreUtils.trim("foo"));
+ Assert.assertEquals("foo", EagleEyeCoreUtils.trim("foo "));
+ Assert.assertEquals("foo", EagleEyeCoreUtils.trim(" foo"));
+ Assert.assertEquals("foo", EagleEyeCoreUtils.trim(" foo "));
+ Assert.assertEquals("f o o", EagleEyeCoreUtils.trim(" f o o "));
+ }
+
+ @Test
+ public void testSplit() {
+ Assert.assertNull(EagleEyeCoreUtils.split(null, 'a'));
+
+ Assert.assertArrayEquals(new String[0],
+ EagleEyeCoreUtils.split("", ','));
+ Assert.assertArrayEquals(new String[]{"foo", "bar", "baz"},
+ EagleEyeCoreUtils.split("foo,bar,baz", ','));
+ }
+
+ @Test
+ public void testAppendWithBlankCheck() {
+ Assert.assertEquals("bar", EagleEyeCoreUtils.appendWithBlankCheck(
+ null, "bar", new StringBuilder()).toString());
+ Assert.assertEquals("foo", EagleEyeCoreUtils.appendWithBlankCheck(
+ "foo", "bar", new StringBuilder()).toString());
+ }
+
+ @Test
+ public void testAppendWithNullCheck() {
+ Assert.assertEquals("bar", EagleEyeCoreUtils.appendWithNullCheck(
+ null, "bar", new StringBuilder()).toString());
+ Assert.assertEquals("foo", EagleEyeCoreUtils.appendWithNullCheck(
+ "foo", "bar", new StringBuilder()).toString());
+ }
+
+ @Test
+ public void testAppendLog() {
+ Assert.assertEquals("foo bar baz foo", EagleEyeCoreUtils.appendLog(
+ "foo\nbar\rbaz,foo", new StringBuilder(), ',').toString());
+ Assert.assertEquals("", EagleEyeCoreUtils.appendLog(
+ null, new StringBuilder(), ',').toString());
+ }
+
+ @Test
+ public void testFormatTime() {
+ Assert.assertEquals("2019-06-15 20:13:14.000",
+ EagleEyeCoreUtils.formatTime(1560600794000L));
+ }
+
+ @Test
+ public void testGetSystemProperty() {
+ Assert.assertNull(EagleEyeCoreUtils.getSystemProperty(null));
+ Assert.assertNull(EagleEyeCoreUtils.getSystemProperty("foo"));
+ }
+
+ @Test
+ public void testGetSystemPropertyForLong() {
+ Assert.assertEquals(2L,
+ EagleEyeCoreUtils.getSystemPropertyForLong(null, 2L));
+ }
+
+ @Test
+ public void testIsHexNumeric() {
+ Assert.assertTrue(EagleEyeCoreUtils.isHexNumeric('2'));
+ Assert.assertTrue(EagleEyeCoreUtils.isHexNumeric('b'));
+
+ Assert.assertFalse(EagleEyeCoreUtils.isHexNumeric('z'));
+ Assert.assertFalse(EagleEyeCoreUtils.isHexNumeric('%'));
+ }
+
+ @Test
+ public void testIsNumeric() {
+ Assert.assertTrue(EagleEyeCoreUtils.isNumeric('2'));
+
+ Assert.assertFalse(EagleEyeCoreUtils.isNumeric('b'));
+ Assert.assertFalse(EagleEyeCoreUtils.isNumeric('%'));
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java
new file mode 100644
index 0000000000..57413603ca
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.log;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test cases for {@link ConsoleHandler}.
+ *
+ * @author cdfive
+ */
+public class ConsoleHandlerTest {
+
+ @Test
+ public void testPublish() {
+ ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(baosOut));
+
+ ByteArrayOutputStream baosErr = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(baosErr));
+
+ CspFormatter cspFormatter = new CspFormatter();
+ ConsoleHandler consoleHandler = new ConsoleHandler();
+
+ LogRecord logRecord;
+
+ // Test INFO level, should log to stdout
+ logRecord = new LogRecord(Level.INFO, "test info message");
+ consoleHandler.publish(logRecord);
+ assertEquals(cspFormatter.format(logRecord), baosOut.toString());
+ assertEquals("", baosErr.toString());
+ baosOut.reset();
+ baosErr.reset();
+
+ // Test INFO level, should log to stderr
+ logRecord = new LogRecord(Level.WARNING, "test warning message");
+ consoleHandler.publish(logRecord);
+ assertEquals(cspFormatter.format(logRecord), baosErr.toString());
+ assertEquals("", baosOut.toString());
+ baosOut.reset();
+ baosErr.reset();
+
+ // Test FINE level, no log by default
+ // Default log level is INFO, to log FINE message to stdout, add following config in $JAVA_HOME/jre/lib/logging.properties
+ // java.util.logging.StreamHandler.level=FINE
+ logRecord = new LogRecord(Level.FINE, "test fine message");
+ consoleHandler.publish(logRecord);
+ assertEquals("", baosOut.toString());
+ assertEquals("", baosErr.toString());
+ baosOut.reset();
+ baosErr.reset();
+
+ // Test SEVERE level, should log to stderr
+ logRecord = new LogRecord(Level.SEVERE, "test severe message");
+ consoleHandler.publish(logRecord);
+ assertEquals(cspFormatter.format(logRecord), baosErr.toString());
+ assertEquals("", baosOut.toString());
+ baosOut.reset();
+ baosErr.reset();
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/LogBaseTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/LogBaseTest.java
new file mode 100644
index 0000000000..5a380956bc
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/LogBaseTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.log;
+
+import org.junit.Assert;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import static com.alibaba.csp.sentinel.log.LogBase.*;
+import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class LogBaseTest {
+
+
+
+ //add Jvm parameter
+ //-Dcsp.sentinel.config.file=log-propertiesTest.properties
+ //-Dcsp.sentinel.log.charset="utf-8"
+ //-Dcsp.sentinel.log.output.type="file"
+ //@Test
+ public void testLoadProperties() throws IOException {
+
+ File file = null;
+ String fileName = "log-propertiesTest.properties";
+ try {
+ file = new File(addSeparator(System.getProperty("user.dir")) + "target/classes/" + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ BufferedWriter out = new BufferedWriter(new FileWriter(file));
+ out.write(buildPropertyStr(LOG_DIR, "/data/logs/"));
+ out.write("\n");
+ out.write(buildPropertyStr(LOG_NAME_USE_PID, "true"));
+ out.write("\n");
+ out.write(buildPropertyStr(LOG_OUTPUT_TYPE, "console"));
+ out.write("\n");
+ out.write(buildPropertyStr(LOG_CHARSET, "gbk"));
+ out.flush();
+ out.close();
+
+ //test will make dir
+ //Assert.assertTrue(LogBase.getLogBaseDir().equals("/data/logs/"));
+ Assert.assertTrue(LogBase.isLogNameUsePid());
+ Assert.assertTrue(LogBase.getLogOutputType().equals("file"));
+ Assert.assertTrue(LogBase.getLogCharset().equals("utf-8"));
+ } finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+
+ }
+
+
+ private String buildPropertyStr(String key, String value) {
+ return key + "=" + value;
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java
index 8bf2b89520..604c94c29e 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.alibaba.csp.sentinel.metric.extension.callback;
import com.alibaba.csp.sentinel.Entry;
@@ -5,18 +20,18 @@
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
-import com.alibaba.csp.sentinel.util.TimeUtil;
+import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest;
import org.junit.Assert;
import org.junit.Test;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.when;
/**
* @author Carpenter Lee
*/
-public class MetricExitCallbackTest {
+public class MetricExitCallbackTest extends AbstractTimeBasedTest {
@Test
public void onExit() {
@@ -27,17 +42,24 @@ public void onExit() {
StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT);
int count = 2;
Object[] args = {"args1", "args2"};
- extension.rt = 20;
+ long prevRt = 20;
+ extension.rt = prevRt;
extension.success = 6;
extension.thread = 10;
Context context = mock(Context.class);
Entry entry = mock(Entry.class);
+
+ // Mock current time
+ long curMillis = System.currentTimeMillis();
+ setCurrentMillis(curMillis);
+
+ int deltaMs = 100;
when(entry.getError()).thenReturn(null);
- when(entry.getCreateTime()).thenReturn(TimeUtil.currentTimeMillis() - 100);
+ when(entry.getCreateTime()).thenReturn(curMillis - deltaMs);
when(context.getCurEntry()).thenReturn(entry);
exitCallback.onExit(context, resourceWrapper, count, args);
- Assert.assertEquals(120, extension.rt, 10);
+ Assert.assertEquals(prevRt + deltaMs, extension.rt);
Assert.assertEquals(extension.success, 6 + count);
Assert.assertEquals(extension.thread, 10 - 1);
}
-}
\ No newline at end of file
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java
index e900cea9f2..9c5710573c 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java
@@ -41,7 +41,7 @@ public class ClusterNodeTest {
@Test
public void testGetOrCreateOriginNodeSingleThread() {
- ClusterNode clusterNode = new ClusterNode();
+ ClusterNode clusterNode = new ClusterNode("test");
String origin1 = "origin1";
Node originNode1 = clusterNode.getOrCreateOriginNode(origin1);
@@ -74,7 +74,7 @@ public void testGetOrCreateOriginNodeMultiThread() {
final int testTimes = 10;
for (int times = 0; times < testTimes; times++) {
- final ClusterNode clusterNode = new ClusterNode();
+ final ClusterNode clusterNode = new ClusterNode("test");
// Store all distinct nodes by calling ClusterNode#getOrCreateOriginNode.
// Here we need a thread-safe concurrent set (created from ConcurrentHashMap).
@@ -130,7 +130,7 @@ public Object call() throws Exception {
@Test
public void testTraceException() {
- ClusterNode clusterNode = new ClusterNode();
+ ClusterNode clusterNode = new ClusterNode("test");
Exception exception = new RuntimeException("test");
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java
index c34b8bf919..d128e6864a 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java
@@ -15,7 +15,7 @@
*/
package com.alibaba.csp.sentinel.node;
-import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.util.TimeUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -111,7 +111,7 @@ public void testStatisticThreadNumAndQps() {
assertEquals(totalRequest, node.totalSuccess());
// now there are no data in time span, so the minRT should be equals to TIME_DROP_VALVE
- assertEquals(node.minRt(), Constants.TIME_DROP_VALVE, 0.01);
+ assertEquals(node.minRt(), SentinelConfig.statisticMaxRt(), 0.01);
log("====================================================");
log("testStatisticThreadNumAndQps done, cost " + (TimeUtil.currentTimeMillis() - testStartTime) + "ms");
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/metric/MetricNodeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/metric/MetricNodeTest.java
new file mode 100644
index 0000000000..9331f0b682
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/metric/MetricNodeTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.node.metric;
+
+import com.alibaba.csp.sentinel.ResourceTypeConstants;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class MetricNodeTest {
+
+ @Test
+ public void testFromFatString() {
+ String line = "1564382218000|2019-07-29 14:36:58|/foo/*|1|0|1|0|0|0|2|1";
+ MetricNode node = MetricNode.fromFatString(line);
+ assertEquals(ResourceTypeConstants.COMMON_WEB, node.getClassification());
+ assertEquals(2, node.getConcurrency());
+ assertEquals(1, node.getSuccessQps());
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
index e90f2206fe..cbe7a951b8 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
@@ -16,6 +16,7 @@
package com.alibaba.csp.sentinel.slots;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot;
@@ -27,6 +28,8 @@
import com.alibaba.csp.sentinel.slots.system.SystemSlot;
import org.junit.Test;
+import java.util.ServiceLoader;
+
import static org.junit.Assert.*;
/**
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java
index 3884773c06..0d73610bd5 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java
@@ -43,11 +43,23 @@ public void testIsValidRule() {
.setCount(-3)
.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT)
.setTimeWindow(2);
+ DegradeRule rule5 = new DegradeRule("Sentinel")
+ .setCount(97)
+ .setGrade(RuleConstant.DEGRADE_GRADE_RT)
+ .setTimeWindow(15)
+ .setRtSlowRequestAmount(0);
+ DegradeRule rule6 = new DegradeRule("Sentinel")
+ .setCount(0.93d)
+ .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO)
+ .setTimeWindow(20)
+ .setMinRequestAmount(0);
assertFalse(DegradeRuleManager.isValidRule(rule1));
assertFalse(DegradeRuleManager.isValidRule(rule2));
assertFalse(DegradeRuleManager.isValidRule(rule3));
assertTrue(DegradeRuleManager.isValidRule(rule3.setCount(1.0d)));
assertTrue(DegradeRuleManager.isValidRule(rule3.setCount(0.0d)));
assertFalse(DegradeRuleManager.isValidRule(rule4));
+ assertFalse(DegradeRuleManager.isValidRule(rule5));
+ assertFalse(DegradeRuleManager.isValidRule(rule6));
}
}
\ No newline at end of file
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java
index b2518a8016..75668c0be9 100755
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java
@@ -15,14 +15,6 @@
*/
package com.alibaba.csp.sentinel.slots.block.degrade;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Test;
-
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.ClusterNode;
@@ -30,6 +22,14 @@
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
* @author jialiang.linjl
@@ -47,12 +47,16 @@ public void testAverageRtDegrade() throws InterruptedException {
when(node.getClusterNode()).thenReturn(cn);
when(cn.avgRt()).thenReturn(2d);
+ int rtSlowRequestAmount = 10;
DegradeRule rule = new DegradeRule();
rule.setCount(1);
rule.setResource(key);
rule.setTimeWindow(2);
+ rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
+ rule.setRtSlowRequestAmount(rtSlowRequestAmount);
- for (int i = 0; i < 4; i++) {
+ //Will true
+ for (int i = 0; i < rtSlowRequestAmount - 1; i++) {
assertTrue(rule.passCheck(context, node, 1));
}
@@ -69,9 +73,6 @@ public void testAverageRtDegrade() throws InterruptedException {
public void testExceptionRatioModeDegrade() throws Throwable {
String key = "test_degrade_exception_ratio";
ClusterNode cn = mock(ClusterNode.class);
- when(cn.exceptionQps()).thenReturn(2d);
- // Indicates that there are QPS more than min threshold.
- when(cn.totalQps()).thenReturn(12d);
ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(key, EntryType.IN), cn);
Context context = mock(Context.class);
@@ -83,17 +84,39 @@ public void testExceptionRatioModeDegrade() throws Throwable {
rule.setResource(key);
rule.setTimeWindow(2);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
+ rule.setMinRequestAmount(20);
- when(cn.successQps()).thenReturn(8d);
- // Will fail.
+ // Will true. While totalQps < minRequestAmount
+ when(cn.totalQps()).thenReturn(8d);
+ assertTrue(rule.passCheck(context, node, 1));
+
+ // Will true.
+ when(cn.totalQps()).thenReturn(21d);
+ when(cn.successQps()).thenReturn(9d);
+ when(cn.exceptionQps()).thenReturn(9d);
+ assertTrue(rule.passCheck(context, node, 1));
+
+
+ // Will true. While totalQps > minRequestAmount and exceptionRation < count
+ when(cn.totalQps()).thenReturn(100d);
+ when(cn.successQps()).thenReturn(90d);
+ when(cn.exceptionQps()).thenReturn(10d);
+ assertTrue(rule.passCheck(context, node, 1));
+
+ // Will fail. While totalQps > minRequestAmount and exceptionRation > count
+ rule.setMinRequestAmount(5);
+ when(cn.totalQps()).thenReturn(12d);
+ when(cn.successQps()).thenReturn(8d);
+ when(cn.exceptionQps()).thenReturn(6d);
assertFalse(rule.passCheck(context, node, 1));
// Restore from the degrade timeout.
TimeUnit.MILLISECONDS.sleep(2200);
- when(cn.successQps()).thenReturn(20d);
// Will pass.
+ when(cn.totalQps()).thenReturn(106d);
+ when(cn.successQps()).thenReturn(100d);
assertTrue(rule.passCheck(context, node, 1));
}
@@ -127,4 +150,33 @@ public void testExceptionCountModeDegrade() throws Throwable {
assertTrue(rule.passCheck(context, node, 1));
}
+ @Test
+ public void testEquals() {
+ DegradeRule degradeRule1 = new DegradeRule();
+ DegradeRule degradeRule2 = new DegradeRule();
+ assertTrue(degradeRule1.equals(degradeRule2));
+
+ int rtSlowRequestAmount = 10;
+ int minRequestAmount = 20;
+ double count = 1.0;
+ int timeWindow = 2;
+ degradeRule1.setRtSlowRequestAmount(rtSlowRequestAmount);
+ degradeRule1.setMinRequestAmount(minRequestAmount);
+ degradeRule1.setCount(count);
+ degradeRule1.setTimeWindow(timeWindow);
+ degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_RT);
+
+ degradeRule2.setRtSlowRequestAmount(rtSlowRequestAmount);
+ degradeRule2.setMinRequestAmount(minRequestAmount);
+ degradeRule2.setCount(count);
+ degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT);
+ degradeRule2.setTimeWindow(timeWindow);
+ assertTrue(degradeRule1.equals(degradeRule2));
+
+ degradeRule2.setMinRequestAmount(100);
+ assertFalse(degradeRule1.equals(degradeRule2));
+
+
+ }
+
}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java
index fd9e9f4318..f9e7dd7029 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java
@@ -1,19 +1,16 @@
package com.alibaba.csp.sentinel.slots.logger;
-import java.io.File;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
import com.alibaba.csp.sentinel.log.LogBase;
import com.alibaba.csp.sentinel.log.RecordLog;
-
-import org.hamcrest.CoreMatchers;
-import org.hamcrest.Matchers;
import org.hamcrest.io.FileMatchers;
+import org.junit.Assert;
import org.junit.Test;
+import java.io.File;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
import static org.awaitility.Awaitility.await;
-import static org.junit.Assert.*;
/**
* @author Carpenter Lee
@@ -34,7 +31,9 @@ public File call() throws Exception {
}, FileMatchers.anExistingFile());
}
- @Test
+ //Change LogBase It is not not work when integration Testing
+ //Because LogBase.LOG_DIR can be just static init for once and it will not be changed
+ //@Test
public void testChangeLogBase() throws Exception {
String userHome = System.getProperty("user.home");
String newLogBase = userHome + File.separator + "tmpLogDir" + System.currentTimeMillis();
@@ -42,13 +41,27 @@ public void testChangeLogBase() throws Exception {
EagleEyeLogUtil.log("resourceName", "BlockException", "app1", "origin", 1);
+
final File file = new File(RecordLog.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME);
await().timeout(2, TimeUnit.SECONDS)
- .until(new Callable() {
- @Override
- public File call() throws Exception {
- return file;
+ .until(new Callable() {
+ @Override
+ public File call() throws Exception {
+ return file;
+ }
+ }, FileMatchers.anExistingFile());
+ Assert.assertTrue(file.getAbsolutePath().startsWith(newLogBase));
+ deleteLogDir(new File(RecordLog.getLogBaseDir()));
+ }
+
+ private void deleteLogDir(File logDirFile) {
+ if (logDirFile != null && logDirFile.isDirectory()) {
+ if (logDirFile.listFiles() != null) {
+ for (File file : logDirFile.listFiles()) {
+ file.delete();
}
- }, FileMatchers.anExistingFile());
+ }
+ logDirFile.delete();
+ }
}
}
\ No newline at end of file
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetricTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetricTest.java
index 80293d4874..2097670e0a 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetricTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetricTest.java
@@ -16,9 +16,14 @@
package com.alibaba.csp.sentinel.slots.statistic.metric;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import com.alibaba.csp.sentinel.node.metric.MetricNode;
+import com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+import com.alibaba.csp.sentinel.util.function.Predicate;
import org.junit.Test;
@@ -38,7 +43,8 @@ public class ArrayMetricTest {
@Test
public void testOperateArrayMetric() {
BucketLeapArray leapArray = mock(BucketLeapArray.class);
- final WindowWrap windowWrap = new WindowWrap(windowLengthInMs, 0, new MetricBucket());
+ final WindowWrap windowWrap = new WindowWrap(windowLengthInMs, 0,
+ new MetricBucket());
when(leapArray.currentWindow()).thenReturn(windowWrap);
when(leapArray.values()).thenReturn(new ArrayList() {{ add(windowWrap.value()); }});
@@ -70,4 +76,45 @@ public void testOperateArrayMetric() {
assertEquals(expectedException, metric.exception());
assertEquals(expectedRt, metric.rt());
}
-}
\ No newline at end of file
+
+ @Test
+ public void testGetMetricDetailsOnCondition() {
+ BucketLeapArray leapArray = mock(BucketLeapArray.class);
+ // Mock interval=2s, sampleCount=2
+ final WindowWrap w1 = new WindowWrap<>(windowLengthInMs, 500,
+ new MetricBucket().add(MetricEvent.PASS, 1));
+ final WindowWrap w2 = new WindowWrap<>(windowLengthInMs, 1000,
+ new MetricBucket().add(MetricEvent.PASS, 2));
+ final WindowWrap w3 = new WindowWrap<>(windowLengthInMs, 1500,
+ new MetricBucket().add(MetricEvent.PASS, 3));
+ final WindowWrap w4 = new WindowWrap<>(windowLengthInMs, 2000,
+ new MetricBucket().add(MetricEvent.PASS, 4));
+ List> buckets = Arrays.asList(w1, w2, w3, w4);
+ when(leapArray.currentWindow()).thenReturn(w4);
+ when(leapArray.list()).thenReturn(buckets);
+
+ ArrayMetric metric = new ArrayMetric(leapArray);
+
+ // Empty condition -> retrieve all
+ assertEquals(4, metric.detailsOnCondition(null).size());
+ // Normal condition
+ List metricNodes = metric.detailsOnCondition(new Predicate() {
+ @Override
+ public boolean test(Long t) {
+ return t >= 1500;
+ }
+ });
+ assertEquals(2, metricNodes.size());
+ assertEquals(3, metricNodes.get(0).getPassQps());
+ assertEquals(4, metricNodes.get(1).getPassQps());
+
+ // Future condition
+ metricNodes = metric.detailsOnCondition(new Predicate() {
+ @Override
+ public boolean test(Long t) {
+ return t >= 2500;
+ }
+ });
+ assertEquals(0, metricNodes.size());
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManagerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManagerTest.java
new file mode 100644
index 0000000000..fcd1f66512
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/system/SystemRuleManagerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.system;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class SystemRuleManagerTest {
+
+ @Test
+ public void testLoadInvalidRules() {
+ SystemRule rule1 = new SystemRule();
+ rule1.setHighestSystemLoad(-0.9d);
+ SystemRule rule2 = new SystemRule();
+ rule2.setHighestCpuUsage(2.7d);
+ SystemRuleManager.loadRules(Arrays.asList(rule1, rule2));
+ assertEquals(0, SystemRuleManager.getRules().size());
+ }
+
+ @Test
+ public void testLoadAndGetRules() {
+ SystemRule rule1 = new SystemRule();
+ rule1.setHighestSystemLoad(1.2d);
+ SystemRule rule2 = new SystemRule();
+ rule2.setMaxThread(17);
+ SystemRule rule3 = new SystemRule();
+ rule3.setHighestCpuUsage(0.7d);
+ SystemRule rule4 = new SystemRule();
+ rule4.setQps(1500);
+ SystemRule rule5 = new SystemRule();
+ rule5.setAvgRt(50);
+ SystemRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3, rule4, rule5));
+ assertEquals(1.2d, SystemRuleManager.getSystemLoadThreshold(), 0.01);
+ assertEquals(17, SystemRuleManager.getMaxThreadThreshold());
+ assertEquals(0.7d, SystemRuleManager.getCpuUsageThreshold(), 0.01);
+ assertEquals(1500, SystemRuleManager.getInboundQpsThreshold(), 0.01);
+ assertEquals(50, SystemRuleManager.getRtThreshold());
+ }
+
+ @Test
+ public void testLoadDuplicateTypeOfRules() {
+ SystemRule rule1 = new SystemRule();
+ rule1.setHighestSystemLoad(1.2d);
+ SystemRule rule2 = new SystemRule();
+ rule2.setHighestSystemLoad(2.3d);
+ SystemRule rule3 = new SystemRule();
+ rule3.setHighestSystemLoad(3.4d);
+ SystemRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3));
+
+ List rules = SystemRuleManager.getRules();
+ assertEquals(1, rules.size());
+ assertEquals(1.2d, rules.get(0).getHighestSystemLoad(), 0.01);
+ assertEquals(1.2d, SystemRuleManager.getSystemLoadThreshold(), 0.01);
+ }
+
+ @Test
+ public void testCheckMaxCpuUsageNotBBR() throws Exception {
+ SystemRule rule1 = new SystemRule();
+ rule1.setHighestCpuUsage(0d);
+ SystemRuleManager.loadRules(Collections.singletonList(rule1));
+
+ // Wait until SystemStatusListener triggered the first CPU usage collecting.
+ Thread.sleep(1500);
+
+ boolean blocked = false;
+ try {
+ SystemRuleManager.checkSystem(new StringResourceWrapper("testCheckMaxCpuUsageNotBBR", EntryType.IN));
+ } catch (BlockException ex) {
+ blocked = true;
+ }
+ assertTrue("The entry should be blocked under SystemRule maxCpuUsage=0", blocked);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ SystemRuleManager.loadRules(new ArrayList());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ SystemRuleManager.loadRules(new ArrayList());
+ }
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/ConfigUtilTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/ConfigUtilTest.java
new file mode 100644
index 0000000000..43094006a1
--- /dev/null
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/ConfigUtilTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Properties;
+
+import static com.alibaba.csp.sentinel.log.LogBase.LOG_DIR;
+import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE;
+import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class ConfigUtilTest {
+
+ @Test
+ public void testLoadProperties() throws IOException {
+
+ File file = null;
+ String logOutputType = "utf-8",
+ dir = "/data/logs/",
+ fileName = "propertiesTest.properties";
+ try {
+ String userDir = System.getProperty("user.dir");
+ file = new File(addSeparator(userDir) + "target/classes/" + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ BufferedWriter out = new BufferedWriter(new FileWriter(file));
+ out.write(LOG_OUTPUT_TYPE + "=" + logOutputType);
+ out.write(System.getProperty("line.separator"));
+ out.write(LOG_DIR + "=" + dir);
+ out.flush();
+ out.close();
+
+ //Load from absolutePath
+ Properties properties = ConfigUtil.loadProperties(file.getAbsolutePath());
+ Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE)));
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+
+ //Load from classPath
+ properties = ConfigUtil.loadProperties(ConfigUtil.CLASSPATH_FILE_FLAG + fileName);
+ Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE)));
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+
+ //Load from relativePath
+ properties = ConfigUtil.loadProperties("target/classes/" + fileName);
+ Assert.assertTrue(logOutputType.equals(properties.getProperty(LOG_OUTPUT_TYPE)));
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+ } finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ }
+
+ //add Jvm parameter
+ //-Dcsp.sentinel.charset="UTF-16"
+ //@Test
+ public void testLoadPropertiesWithCustomizedCharset() throws IOException {
+
+ String charset = "UTF-16";
+
+ File file = null;
+ String dir = "/data/logs/",
+ fileName = "propertiesTest.properties";
+ try {
+ String userDir = System.getProperty("user.dir");
+ file = new File(addSeparator(userDir) + "target/classes/" + fileName);
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), charset);
+ out.write(LOG_DIR + "=" + dir);
+ out.flush();
+ out.close();
+
+ //Load from absolutePath
+ Properties properties = ConfigUtil.loadProperties(file.getAbsolutePath());
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+ //Load from classPath
+ properties = ConfigUtil.loadProperties(ConfigUtil.CLASSPATH_FILE_FLAG + fileName);
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+ //Load from relativePath
+ properties = ConfigUtil.loadProperties("target/classes/" + fileName);
+ Assert.assertTrue(dir.equals(properties.getProperty(LOG_DIR)));
+
+ } finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ }
+
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java
index 35944be4b5..e8127303a0 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java
@@ -106,9 +106,41 @@ public void testLoadInstanceListSorted() {
for (int i = 0; i < sortedSlots.size(); i++) {
ProcessorSlot slot = sortedSlots.get(i);
ProcessorSlot slot2 = sortedSlots2.get(i);
+ assertEquals(slot.getClass(), slot2.getClass());
+ }
+ }
+
+ @Test
+ public void testLoadDifferentInstanceListSorted() {
+ List sortedSlots = SpiLoader.loadInstanceListSorted(ProcessorSlot.class);
+ assertNotNull(sortedSlots);
+
+ // Total 8 default slot in sentinel-core
+ assertEquals(8, sortedSlots.size());
+
+ // Verify the order of slot
+ int index = 0;
+ assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot);
+ assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot);
+ assertTrue(sortedSlots.get(index++) instanceof LogSlot);
+ assertTrue(sortedSlots.get(index++) instanceof StatisticSlot);
+ assertTrue(sortedSlots.get(index++) instanceof SystemSlot);
+ assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot);
+ assertTrue(sortedSlots.get(index++) instanceof FlowSlot);
+ assertTrue(sortedSlots.get(index++) instanceof DegradeSlot);
+
+ // Verify each call return different instances
+ List sortedSlots2 = SpiLoader.loadDifferentInstanceListSorted(ProcessorSlot.class);
+ assertNotSame(sortedSlots, sortedSlots2);
+ assertEquals(sortedSlots.size(), sortedSlots2.size());
+ for (int i = 0; i < sortedSlots.size(); i++) {
+ ProcessorSlot slot = sortedSlots.get(i);
+ ProcessorSlot slot2 = sortedSlots2.get(i);
+ assertEquals(slot.getClass(), slot2.getClass());
+
+ // Verify the instances are different
assertNotSame(slot, slot2);
assertNotEquals(slot, slot2);
- assertEquals(slot.getClass(), slot2.getClass());
}
}
}
diff --git a/sentinel-dashboard/pom.xml b/sentinel-dashboard/pom.xml
index b8bafb1090..fcddca45af 100755
--- a/sentinel-dashboard/pom.xml
+++ b/sentinel-dashboard/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.cspsentinel-parent
- 1.7.0-SNAPSHOT
+ 1.7.2-SNAPSHOTsentinel-dashboard
@@ -38,6 +38,11 @@
sentinel-parameter-flow-control${project.version}
+
+ com.alibaba.csp
+ sentinel-api-gateway-adapter-common
+ ${project.version}
+ org.springframework.boot
@@ -175,6 +180,7 @@
src/main/resources
+ true
diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthAction.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthAction.java
new file mode 100644
index 0000000000..f521c04dfa
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.dashboard.auth;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author lkxiaolou
+ * @since 1.7.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ElementType.METHOD})
+public @interface AuthAction {
+
+ /**
+ * @return the privilege type
+ */
+ AuthService.PrivilegeType value();
+
+ /**
+ * @return the target name to control
+ */
+ String targetName() default "app";
+
+ /**
+ * @return the message when permission is denied
+ */
+ String message() default "Permission denied";
+}
diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthorizationInterceptor.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthorizationInterceptor.java
new file mode 100644
index 0000000000..19472521ae
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/AuthorizationInterceptor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.dashboard.auth;
+
+import com.alibaba.csp.sentinel.dashboard.domain.Result;
+import com.alibaba.fastjson.JSON;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * The web interceptor for privilege-based authorization.
+ *
+ * @author lkxiaolou
+ * @since 1.7.1
+ */
+@Component
+public class AuthorizationInterceptor implements HandlerInterceptor {
+
+ @Autowired
+ private AuthService authService;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+ if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
+ Method method = ((HandlerMethod) handler).getMethod();
+
+ AuthAction authAction = method.getAnnotation(AuthAction.class);
+ if (authAction != null) {
+ AuthService.AuthUser authUser = authService.getAuthUser(request);
+ if (authUser == null) {
+ responseNoPrivilegeMsg(response, authAction.message());
+ return false;
+ }
+ String target = request.getParameter(authAction.targetName());
+
+ if (!authUser.authTarget(target, authAction.value())) {
+ responseNoPrivilegeMsg(response, authAction.message());
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void responseNoPrivilegeMsg(HttpServletResponse response, String message) throws IOException {
+ Result result = Result.ofFail(-1, message);
+ response.addHeader("Content-Type", "application/json;charset=UTF-8");
+ response.getOutputStream().write(JSON.toJSONBytes(result));
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/filter/AuthFilter.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/LoginAuthenticationFilter.java
similarity index 74%
rename from sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/filter/AuthFilter.java
rename to sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/LoginAuthenticationFilter.java
index 45972c4f15..f489b48e7f 100644
--- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/filter/AuthFilter.java
+++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/auth/LoginAuthenticationFilter.java
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.alibaba.csp.sentinel.dashboard.filter;
+package com.alibaba.csp.sentinel.dashboard.auth;
-import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -30,39 +29,46 @@
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+
import java.io.IOException;
import java.util.List;
/**
- * Servlet Filter that authenticate requests.
- *
- * Note:
- * Some urls are excluded as they needn't auth, such as:
+ *
The Servlet filter for authentication.
*
- * Index url: /
- * Authentication request url: /login,logout
- * Used for client: /registry/machine
- * Static resources: htm,html,js and so on.
+ *
Note: some urls are excluded as they needn't auth, such as: