- Enhance rule checking in rule managers and Sentinel Dashboard frontend - Enhance error information when reporting invalid fields in rule dialog Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -151,6 +151,7 @@ public final class AuthorityRuleManager { | |||||
} | } | ||||
static boolean isValidRule(AuthorityRule rule) { | static boolean isValidRule(AuthorityRule rule) { | ||||
return rule != null && !StringUtil.isBlank(rule.getResource()); | |||||
return rule != null && !StringUtil.isBlank(rule.getResource()) | |||||
&& rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp()); | |||||
} | } | ||||
} | } |
@@ -59,6 +59,12 @@ public class DegradeRule extends AbstractRule { | |||||
private static ScheduledExecutorService pool = Executors.newScheduledThreadPool( | private static ScheduledExecutorService pool = Executors.newScheduledThreadPool( | ||||
Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true)); | Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true)); | ||||
public DegradeRule() {} | |||||
public DegradeRule(String resourceName) { | |||||
setResource(resourceName); | |||||
} | |||||
/** | /** | ||||
* RT threshold or exception ratio threshold count. | * RT threshold or exception ratio threshold count. | ||||
*/ | */ | ||||
@@ -80,8 +86,9 @@ public class DegradeRule extends AbstractRule { | |||||
return grade; | return grade; | ||||
} | } | ||||
public void setGrade(int grade) { | |||||
public DegradeRule setGrade(int grade) { | |||||
this.grade = grade; | this.grade = grade; | ||||
return this; | |||||
} | } | ||||
private AtomicLong passCount = new AtomicLong(0); | private AtomicLong passCount = new AtomicLong(0); | ||||
@@ -92,8 +99,9 @@ public class DegradeRule extends AbstractRule { | |||||
return count; | return count; | ||||
} | } | ||||
public void setCount(double count) { | |||||
public DegradeRule setCount(double count) { | |||||
this.count = count; | this.count = count; | ||||
return this; | |||||
} | } | ||||
public boolean isCut() { | public boolean isCut() { | ||||
@@ -112,8 +120,9 @@ public class DegradeRule extends AbstractRule { | |||||
return timeWindow; | return timeWindow; | ||||
} | } | ||||
public void setTimeWindow(int timeWindow) { | |||||
public DegradeRule setTimeWindow(int timeWindow) { | |||||
this.timeWindow = timeWindow; | this.timeWindow = timeWindow; | ||||
return this; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -167,7 +167,16 @@ public class DegradeRuleManager { | |||||
} | } | ||||
private static boolean isValidRule(DegradeRule rule) { | |||||
return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0; | |||||
public static boolean isValidRule(DegradeRule rule) { | |||||
boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) | |||||
&& rule.getCount() >= 0 && rule.getTimeWindow() > 0; | |||||
if (!baseValid) { | |||||
return false; | |||||
} | |||||
// Check exception ratio mode. | |||||
if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO && rule.getCount() > 1) { | |||||
return false; | |||||
} | |||||
return true; | |||||
} | } | ||||
} | } |
@@ -202,7 +202,31 @@ public class FlowRuleManager { | |||||
} | } | ||||
private static boolean isValidRule(FlowRule rule) { | |||||
return rule != null && !StringUtil.isBlank(rule.getResource()); | |||||
public static boolean isValidRule(FlowRule rule) { | |||||
boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 | |||||
&& rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; | |||||
if (!baseValid) { | |||||
return false; | |||||
} | |||||
// Check strategy and control (shaping) behavior. | |||||
return checkStrategyField(rule) && checkControlBehaviorField(rule); | |||||
} | |||||
private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) { | |||||
if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) { | |||||
return StringUtil.isNotBlank(rule.getRefResource()); | |||||
} | |||||
return true; | |||||
} | |||||
private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) { | |||||
switch (rule.getControlBehavior()) { | |||||
case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: | |||||
return rule.getWarmUpPeriodSec() > 0; | |||||
case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: | |||||
return rule.getMaxQueueingTimeMs() > 0; | |||||
default: | |||||
return true; | |||||
} | |||||
} | } | ||||
} | } |
@@ -47,10 +47,13 @@ public class AuthorityRuleManagerTest { | |||||
AuthorityRule ruleB = null; | AuthorityRule ruleB = null; | ||||
AuthorityRule ruleC = new AuthorityRule(); | AuthorityRule ruleC = new AuthorityRule(); | ||||
ruleC.setResource("abc"); | ruleC.setResource("abc"); | ||||
AuthorityRule ruleD = new AuthorityRule(); | |||||
ruleD.setResource("bcd").setLimitApp("abc"); | |||||
assertFalse(AuthorityRuleManager.isValidRule(ruleA)); | assertFalse(AuthorityRuleManager.isValidRule(ruleA)); | ||||
assertFalse(AuthorityRuleManager.isValidRule(ruleB)); | assertFalse(AuthorityRuleManager.isValidRule(ruleB)); | ||||
assertTrue(AuthorityRuleManager.isValidRule(ruleC)); | |||||
assertFalse(AuthorityRuleManager.isValidRule(ruleC)); | |||||
assertTrue(AuthorityRuleManager.isValidRule(ruleD)); | |||||
} | } | ||||
@After | @After | ||||
@@ -0,0 +1,53 @@ | |||||
/* | |||||
* 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.block.degrade; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* Test cases for {@link DegradeRuleManager}. | |||||
* | |||||
* @author Eric Zhao | |||||
*/ | |||||
public class DegradeRuleManagerTest { | |||||
@Test | |||||
public void testIsValidRule() { | |||||
DegradeRule rule1 = new DegradeRule("abc"); | |||||
DegradeRule rule2 = new DegradeRule("cde") | |||||
.setCount(100) | |||||
.setGrade(RuleConstant.DEGRADE_GRADE_RT) | |||||
.setTimeWindow(-1); | |||||
DegradeRule rule3 = new DegradeRule("xx") | |||||
.setCount(1.1) | |||||
.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) | |||||
.setTimeWindow(2); | |||||
DegradeRule rule4 = new DegradeRule("yy") | |||||
.setCount(-3) | |||||
.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) | |||||
.setTimeWindow(2); | |||||
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)); | |||||
} | |||||
} |
@@ -81,13 +81,41 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia | |||||
}); | }); | ||||
}; | }; | ||||
function checkRuleValid(rule) { | |||||
if (rule.resource === undefined || rule.resource === '') { | |||||
alert('资源名称不能为空'); | |||||
return false; | |||||
} | |||||
if (rule.grade === undefined || rule.grade < 0) { | |||||
alert('未知的降级类型'); | |||||
return false; | |||||
} | |||||
if (rule.count === undefined || rule.count === '' || rule.count < 0) { | |||||
alert('降级阈值不能为空或小于 0'); | |||||
return false; | |||||
} | |||||
if (rule.timeWindow === undefined || rule.timeWindow === '' || rule.timeWindow <= 0) { | |||||
alert('降级时间窗口必须大于 0'); | |||||
return false; | |||||
} | |||||
// 异常比率类型. | |||||
if (rule.grade == 1 && rule.count > 1) { | |||||
alert('异常比率超出范围:[0.0 - 1.0]'); | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
$scope.saveRule = function () { | $scope.saveRule = function () { | ||||
if ($scope.degradeRuleDialog.type == 'add') { | |||||
if (!checkRuleValid($scope.currentRule)) { | |||||
return; | |||||
} | |||||
if ($scope.degradeRuleDialog.type === 'add') { | |||||
addNewRule($scope.currentRule); | addNewRule($scope.currentRule); | ||||
} else if ($scope.degradeRuleDialog.type == 'edit') { | |||||
} else if ($scope.degradeRuleDialog.type === 'edit') { | |||||
saveRule($scope.currentRule, true); | saveRule($scope.currentRule, true); | ||||
} | } | ||||
} | |||||
}; | |||||
var confirmDialog; | var confirmDialog; | ||||
$scope.deleteRule = function (rule) { | $scope.deleteRule = function (rule) { | ||||
@@ -88,13 +88,58 @@ app.controller('FlowCtl', ['$scope', '$stateParams', 'FlowService', 'ngDialog', | |||||
}); | }); | ||||
}; | }; | ||||
function notNumberAtLeastZero(num) { | |||||
return num === undefined || num === '' || isNaN(num) || num < 0; | |||||
} | |||||
function notNumberGreaterThanZero(num) { | |||||
return num === undefined || num === '' || isNaN(num) || num <= 0; | |||||
} | |||||
function checkRuleValid(rule) { | |||||
if (rule.resource === undefined || rule.resource === '') { | |||||
alert('资源名称不能为空'); | |||||
return false; | |||||
} | |||||
if (rule.count === undefined || rule.count < 0) { | |||||
alert('限流阈值必须大于等于 0'); | |||||
return false; | |||||
} | |||||
if (rule.strategy === undefined || rule.strategy < 0) { | |||||
alert('无效的流控模式'); | |||||
return false; | |||||
} | |||||
if (rule.strategy == 1 || rule.strategy == 2) { | |||||
if (rule.refResource === undefined || rule.refResource === '') { | |||||
alert('请填写关联资源或入口'); | |||||
return false; | |||||
} | |||||
} | |||||
if (rule.controlBehavior === undefined || rule.controlBehavior < 0) { | |||||
alert('无效的流控整形方式'); | |||||
return false; | |||||
} | |||||
if (rule.controlBehavior == 1 && notNumberGreaterThanZero(rule.warmUpPeriodSec)) { | |||||
alert('预热时长必须大于 0'); | |||||
return false; | |||||
} | |||||
if (rule.controlBehavior == 2 && notNumberGreaterThanZero(rule.maxQueueingTimeMs)) { | |||||
alert('排队超时时间必须大于 0'); | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
$scope.saveRule = function () { | $scope.saveRule = function () { | ||||
if ($scope.flowRuleDialog.type == 'add') { | |||||
if (!checkRuleValid($scope.currentRule)) { | |||||
return; | |||||
} | |||||
if ($scope.flowRuleDialog.type === 'add') { | |||||
addNewRule($scope.currentRule); | addNewRule($scope.currentRule); | ||||
} else if ($scope.flowRuleDialog.type == 'edit') { | |||||
} else if ($scope.flowRuleDialog.type === 'edit') { | |||||
saveRule($scope.currentRule, true); | saveRule($scope.currentRule, true); | ||||
} | } | ||||
} | |||||
}; | |||||
var confirmDialog; | var confirmDialog; | ||||
$scope.deleteRule = function (rule) { | $scope.deleteRule = function (rule) { | ||||
@@ -115,7 +160,7 @@ app.controller('FlowCtl', ['$scope', '$stateParams', 'FlowService', 'ngDialog', | |||||
}; | }; | ||||
$scope.confirm = function () { | $scope.confirm = function () { | ||||
if ($scope.confirmDialog.type == 'delete_rule') { | |||||
if ($scope.confirmDialog.type === 'delete_rule') { | |||||
deleteRule($scope.currentRule); | deleteRule($scope.currentRule); | ||||
} else { | } else { | ||||
console.error('error'); | console.error('error'); | ||||
@@ -42,7 +42,7 @@ | |||||
</div> | </div> | ||||
<label class="col-sm-2 control-label">时间窗口</label> | <label class="col-sm-2 control-label">时间窗口</label> | ||||
<div class="col-sm-4"> | <div class="col-sm-4"> | ||||
<input type='number' class="form-control highlight-border" ng-model='currentRule.timeWindow' placeholder="降级时间间隔, 单位秒" /> | |||||
<input type='number' min="0" class="form-control highlight-border" ng-model='currentRule.timeWindow' placeholder="降级时间间隔, 单位秒" /> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
@@ -15,7 +15,7 @@ | |||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label class="col-sm-2 control-label">流控应用</label> | |||||
<label class="col-sm-2 control-label" title="流控针对应用,即流量入口的调用来源(origin)">流控应用</label> | |||||
<div class="col-sm-9"> | <div class="col-sm-9"> | ||||
<input type="text" class="form-control highlight-border" ng-model='currentRule.limitApp' placeholder='指调用方,"default"表示所有应用。' | <input type="text" class="form-control highlight-border" ng-model='currentRule.limitApp' placeholder='指调用方,"default"表示所有应用。' | ||||
/> | /> | ||||
@@ -32,7 +32,7 @@ | |||||
</div> | </div> | ||||
<label class="col-sm-2 control-label">单机阈值</label> | <label class="col-sm-2 control-label">单机阈值</label> | ||||
<div class="col-sm-3"> | <div class="col-sm-3"> | ||||
<input type='number' class="form-control highlight-border" ng-model='currentRule.count' placeholder="单机阈值" /> | |||||
<input type='number' min="0" class="form-control highlight-border" ng-model='currentRule.count' placeholder="单机阈值" /> | |||||
</div> | </div> | ||||
</div> | </div> | ||||