- Separate converted parameter rules from ParamFlowManager. Now the converted rules will be kept in GatewayRuleManager directly. - Add a GatewayFlowSlot to do separate flow checking for generated rules. - Refactor rule converting mechanism: now gateway rules in normal mode (without parameter) will also be converted to a parameter flow rule. The index will be the last (the last position). In GatewayParamParser we put a constant value to the last position. Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -37,5 +37,15 @@ | |||||
<artifactId>junit</artifactId> | <artifactId>junit</artifactId> | ||||
<scope>test</scope> | <scope>test</scope> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.assertj</groupId> | |||||
<artifactId>assertj-core</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-core</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
</project> | </project> |
@@ -40,6 +40,7 @@ public final class SentinelGatewayConstants { | |||||
public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$"; | 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 = "$$not_match"; | ||||
public static final String GATEWAY_DEFAULT_PARAM = "$D"; | |||||
private SentinelGatewayConstants() {} | private SentinelGatewayConstants() {} | ||||
} | } |
@@ -53,10 +53,13 @@ public class GatewayParamParser<T> { | |||||
} | } | ||||
Set<GatewayFlowRule> gatewayRules = new HashSet<>(); | Set<GatewayFlowRule> gatewayRules = new HashSet<>(); | ||||
Set<Boolean> predSet = new HashSet<>(); | Set<Boolean> predSet = new HashSet<>(); | ||||
boolean hasNonParamRule = false; | |||||
for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) { | for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) { | ||||
if (rule.getParamItem() != null) { | if (rule.getParamItem() != null) { | ||||
gatewayRules.add(rule); | gatewayRules.add(rule); | ||||
predSet.add(rulePredicate.test(rule)); | predSet.add(rulePredicate.test(rule)); | ||||
} else { | |||||
hasNonParamRule = true; | |||||
} | } | ||||
} | } | ||||
if (gatewayRules.isEmpty()) { | if (gatewayRules.isEmpty()) { | ||||
@@ -65,13 +68,17 @@ public class GatewayParamParser<T> { | |||||
if (predSet.size() != 1 || predSet.contains(false)) { | if (predSet.size() != 1 || predSet.contains(false)) { | ||||
return new Object[0]; | return new Object[0]; | ||||
} | } | ||||
Object[] arr = new Object[gatewayRules.size()]; | |||||
int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size(); | |||||
Object[] arr = new Object[size]; | |||||
for (GatewayFlowRule rule : gatewayRules) { | for (GatewayFlowRule rule : gatewayRules) { | ||||
GatewayParamFlowItem paramItem = rule.getParamItem(); | GatewayParamFlowItem paramItem = rule.getParamItem(); | ||||
int idx = paramItem.getIndex(); | int idx = paramItem.getIndex(); | ||||
String param = parseInternal(paramItem, request); | String param = parseInternal(paramItem, request); | ||||
arr[idx] = param; | arr[idx] = param; | ||||
} | } | ||||
if (hasNonParamRule) { | |||||
arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM; | |||||
} | |||||
return arr; | return arr; | ||||
} | } | ||||
@@ -32,6 +32,17 @@ final class GatewayRuleConverter { | |||||
.setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs()); | .setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs()); | ||||
} | } | ||||
static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) { | |||||
return new ParamFlowRule(gatewayRule.getResource()) | |||||
.setCount(gatewayRule.getCount()) | |||||
.setGrade(gatewayRule.getGrade()) | |||||
.setDurationInSec(gatewayRule.getIntervalSec()) | |||||
.setBurstCount(gatewayRule.getBurst()) | |||||
.setControlBehavior(gatewayRule.getControlBehavior()) | |||||
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs()) | |||||
.setParamIdx(idx); | |||||
} | |||||
/** | /** | ||||
* Convert a gateway rule to parameter flow rule, then apply the generated | * Convert a gateway rule to parameter flow rule, then apply the generated | ||||
* parameter index to {@link GatewayParamFlowItem} of the rule. | * parameter index to {@link GatewayParamFlowItem} of the rule. | ||||
@@ -29,10 +29,9 @@ import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | |||||
import com.alibaba.csp.sentinel.property.PropertyListener; | import com.alibaba.csp.sentinel.property.PropertyListener; | ||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | import com.alibaba.csp.sentinel.property.SentinelProperty; | ||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; | |||||
import com.alibaba.csp.sentinel.util.AssertUtil; | import com.alibaba.csp.sentinel.util.AssertUtil; | ||||
import com.alibaba.csp.sentinel.util.StringUtil; | import com.alibaba.csp.sentinel.util.StringUtil; | ||||
@@ -42,7 +41,12 @@ import com.alibaba.csp.sentinel.util.StringUtil; | |||||
*/ | */ | ||||
public final class GatewayRuleManager { | public final class GatewayRuleManager { | ||||
private static final Map<String, Set<GatewayFlowRule>> RULE_MAP = new ConcurrentHashMap<>(); | |||||
/** | |||||
* Gateway flow rule map: (resource, [rules...]) | |||||
*/ | |||||
private static final Map<String, Set<GatewayFlowRule>> GATEWAY_RULE_MAP = new ConcurrentHashMap<>(); | |||||
private static final Map<String, List<ParamFlowRule>> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>(); | |||||
private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); | private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); | ||||
private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>(); | private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>(); | ||||
@@ -74,46 +78,69 @@ public final class GatewayRuleManager { | |||||
public static Set<GatewayFlowRule> getRules() { | public static Set<GatewayFlowRule> getRules() { | ||||
Set<GatewayFlowRule> rules = new HashSet<>(); | Set<GatewayFlowRule> rules = new HashSet<>(); | ||||
for (Set<GatewayFlowRule> ruleSet : RULE_MAP.values()) { | |||||
for (Set<GatewayFlowRule> ruleSet : GATEWAY_RULE_MAP.values()) { | |||||
rules.addAll(ruleSet); | rules.addAll(ruleSet); | ||||
} | } | ||||
return rules; | return rules; | ||||
} | } | ||||
public static Set<GatewayFlowRule> getRulesForResource(String resourceName) { | public static Set<GatewayFlowRule> getRulesForResource(String resourceName) { | ||||
AssertUtil.assertNotBlank(resourceName, "resourceName cannot be blank"); | |||||
Set<GatewayFlowRule> set = RULE_MAP.get(resourceName); | |||||
if (StringUtil.isBlank(resourceName)) { | |||||
return new HashSet<>(); | |||||
} | |||||
Set<GatewayFlowRule> set = GATEWAY_RULE_MAP.get(resourceName); | |||||
if (set == null) { | if (set == null) { | ||||
return new HashSet<>(); | return new HashSet<>(); | ||||
} | } | ||||
return new HashSet<>(set); | return new HashSet<>(set); | ||||
} | } | ||||
/** | |||||
* <p>Get all converted parameter rules.</p> | |||||
* <p>Note: caller SHOULD NOT modify the list and rules.</p> | |||||
* | |||||
* @param resourceName valid resource name | |||||
* @return converted parameter rules | |||||
*/ | |||||
public static List<ParamFlowRule> getConvertedParamRules(String resourceName) { | |||||
if (StringUtil.isBlank(resourceName)) { | |||||
return new ArrayList<>(); | |||||
} | |||||
return CONVERTED_PARAM_RULE_MAP.get(resourceName); | |||||
} | |||||
private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> { | private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> { | ||||
@Override | @Override | ||||
public void configUpdate(Set<GatewayFlowRule> conf) { | public void configUpdate(Set<GatewayFlowRule> conf) { | ||||
applyGatewayRuleInternal(conf); | applyGatewayRuleInternal(conf); | ||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + RULE_MAP); | |||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP); | |||||
} | } | ||||
@Override | @Override | ||||
public void configLoad(Set<GatewayFlowRule> conf) { | public void configLoad(Set<GatewayFlowRule> conf) { | ||||
applyGatewayRuleInternal(conf); | applyGatewayRuleInternal(conf); | ||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + RULE_MAP); | |||||
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + GATEWAY_RULE_MAP); | |||||
} | |||||
private int getIdxInternal(Map<String, Integer> idxMap, String resourceName) { | |||||
// Prepare index map. | |||||
if (!idxMap.containsKey(resourceName)) { | |||||
idxMap.put(resourceName, 0); | |||||
} | |||||
return idxMap.get(resourceName); | |||||
} | } | ||||
private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) { | private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) { | ||||
if (conf == null || conf.isEmpty()) { | if (conf == null || conf.isEmpty()) { | ||||
FlowRuleManager.loadRules(new ArrayList<FlowRule>()); | |||||
ParamFlowRuleManager.loadRules(new ArrayList<ParamFlowRule>()); | |||||
RULE_MAP.clear(); | |||||
applyToConvertedParamMap(new HashSet<ParamFlowRule>()); | |||||
GATEWAY_RULE_MAP.clear(); | |||||
return; | return; | ||||
} | } | ||||
Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>(); | Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>(); | ||||
Map<String, Integer> idxMap = new HashMap<>(); | Map<String, Integer> idxMap = new HashMap<>(); | ||||
List<FlowRule> flowRules = new ArrayList<>(); | |||||
Set<ParamFlowRule> paramFlowRules = new HashSet<>(); | Set<ParamFlowRule> paramFlowRules = new HashSet<>(); | ||||
Map<String, List<GatewayFlowRule>> noParamMap = new HashMap<>(); | |||||
for (GatewayFlowRule rule : conf) { | for (GatewayFlowRule rule : conf) { | ||||
if (!isValidRule(rule)) { | if (!isValidRule(rule)) { | ||||
@@ -122,14 +149,15 @@ public final class GatewayRuleManager { | |||||
} | } | ||||
String resourceName = rule.getResource(); | String resourceName = rule.getResource(); | ||||
if (rule.getParamItem() == null) { | if (rule.getParamItem() == null) { | ||||
// If param item is absent, it will be converted to normal flow rule. | |||||
flowRules.add(GatewayRuleConverter.toFlowRule(rule)); | |||||
} else { | |||||
// Prepare index map. | |||||
if (!idxMap.containsKey(resourceName)) { | |||||
idxMap.put(resourceName, 0); | |||||
// Cache the rules with no parameter config, then skip. | |||||
List<GatewayFlowRule> noParamList = noParamMap.get(resourceName); | |||||
if (noParamList == null) { | |||||
noParamList = new ArrayList<>(); | |||||
noParamMap.put(resourceName, noParamList); | |||||
} | } | ||||
int idx = idxMap.get(resourceName); | |||||
noParamList.add(rule); | |||||
} else { | |||||
int idx = getIdxInternal(idxMap, resourceName); | |||||
// Convert to parameter flow rule. | // Convert to parameter flow rule. | ||||
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) { | if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) { | ||||
idxMap.put(rule.getResource(), idx + 1); | idxMap.put(rule.getResource(), idx + 1); | ||||
@@ -143,11 +171,51 @@ public final class GatewayRuleManager { | |||||
} | } | ||||
ruleSet.add(rule); | ruleSet.add(rule); | ||||
} | } | ||||
FlowRuleManager.loadRules(flowRules); | |||||
ParamFlowRuleManager.loadRules(new ArrayList<>(paramFlowRules)); | |||||
// Handle non-param mode rules. | |||||
for (Map.Entry<String, List<GatewayFlowRule>> e : noParamMap.entrySet()) { | |||||
List<GatewayFlowRule> rules = e.getValue(); | |||||
if (rules == null || rules.isEmpty()) { | |||||
continue; | |||||
} | |||||
for (GatewayFlowRule rule : rules) { | |||||
int idx = getIdxInternal(idxMap, e.getKey()); | |||||
// Always use the same index (the last position). | |||||
paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx)); | |||||
} | |||||
} | |||||
applyToConvertedParamMap(paramFlowRules); | |||||
GATEWAY_RULE_MAP.clear(); | |||||
GATEWAY_RULE_MAP.putAll(gatewayRuleMap); | |||||
} | |||||
private void applyToConvertedParamMap(Set<ParamFlowRule> paramFlowRules) { | |||||
Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap( | |||||
new ArrayList<>(paramFlowRules)); | |||||
if (newRuleMap == null || newRuleMap.isEmpty()) { | |||||
// No parameter flow rules, so clear all the metrics. | |||||
for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) { | |||||
ParameterMetricStorage.clearParamMetricForResource(resource); | |||||
} | |||||
RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules"); | |||||
CONVERTED_PARAM_RULE_MAP.clear(); | |||||
return; | |||||
} | |||||
// Clear unused parameter metrics. | |||||
Set<String> previousResources = CONVERTED_PARAM_RULE_MAP.keySet(); | |||||
for (String resource : previousResources) { | |||||
if (!newRuleMap.containsKey(resource)) { | |||||
ParameterMetricStorage.clearParamMetricForResource(resource); | |||||
} | |||||
} | |||||
// Apply to converted rule map. | |||||
CONVERTED_PARAM_RULE_MAP.clear(); | |||||
CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap); | |||||
RULE_MAP.clear(); | |||||
RULE_MAP.putAll(gatewayRuleMap); | |||||
RecordLog.info("[GatewayRuleManager] Converted internal param rules: " + CONVERTED_PARAM_RULE_MAP); | |||||
} | } | ||||
} | } | ||||
@@ -0,0 +1,75 @@ | |||||
/* | |||||
* 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.slot; | |||||
import java.util.List; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.1 | |||||
*/ | |||||
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
@Override | |||||
public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count, | |||||
boolean prioritized, Object... args) throws Throwable { | |||||
checkGatewayParamFlow(resource, count, args); | |||||
fireEntry(context, resource, node, count, prioritized, args); | |||||
} | |||||
private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args) | |||||
throws BlockException { | |||||
if (args == null) { | |||||
return; | |||||
} | |||||
List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName()); | |||||
if (rules == null || rules.isEmpty()) { | |||||
return; | |||||
} | |||||
for (ParamFlowRule rule : rules) { | |||||
// Initialize the parameter metrics. | |||||
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule); | |||||
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) { | |||||
String triggeredParam = ""; | |||||
if (args.length > rule.getParamIdx()) { | |||||
Object value = args[rule.getParamIdx()]; | |||||
triggeredParam = String.valueOf(value); | |||||
} | |||||
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule); | |||||
} | |||||
} | |||||
} | |||||
@Override | |||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { | |||||
fireExit(context, resourceWrapper, count, args); | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
* 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.slot; | |||||
import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; | |||||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; | |||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; | |||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||||
import com.alibaba.csp.sentinel.slots.logger.LogSlot; | |||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; | |||||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; | |||||
import com.alibaba.csp.sentinel.slots.system.SystemSlot; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.6.1 | |||||
*/ | |||||
public class GatewaySlotChainBuilder implements SlotChainBuilder { | |||||
@Override | |||||
public ProcessorSlotChain build() { | |||||
ProcessorSlotChain chain = new DefaultProcessorSlotChain(); | |||||
// Prepare slot | |||||
chain.addLast(new NodeSelectorSlot()); | |||||
chain.addLast(new ClusterBuilderSlot()); | |||||
// Stat slot | |||||
chain.addLast(new LogSlot()); | |||||
chain.addLast(new StatisticSlot()); | |||||
// Rule checking slot | |||||
chain.addLast(new AuthoritySlot()); | |||||
chain.addLast(new SystemSlot()); | |||||
chain.addLast(new GatewayFlowSlot()); | |||||
chain.addLast(new ParamFlowSlot()); | |||||
chain.addLast(new FlowSlot()); | |||||
chain.addLast(new DegradeSlot()); | |||||
return chain; | |||||
} | |||||
} |
@@ -0,0 +1 @@ | |||||
com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewaySlotChainBuilder |
@@ -0,0 +1,205 @@ | |||||
/* | |||||
* 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.adapter.gateway.common.param; | |||||
import java.util.HashMap; | |||||
import java.util.HashSet; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.util.function.Predicate; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.mockito.ArgumentMatchers.*; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
/** | |||||
* @author Eric Zhao | |||||
*/ | |||||
@SuppressWarnings("unchecked") | |||||
public class GatewayParamParserTest { | |||||
private final Predicate<GatewayFlowRule> routeIdPredicate = new Predicate<GatewayFlowRule>() { | |||||
@Override | |||||
public boolean test(GatewayFlowRule e) { | |||||
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; | |||||
} | |||||
}; | |||||
private final Predicate<GatewayFlowRule> apiNamePredicate = new Predicate<GatewayFlowRule>() { | |||||
@Override | |||||
public boolean test(GatewayFlowRule e) { | |||||
return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME; | |||||
} | |||||
}; | |||||
@Test | |||||
public void testParseParametersNoParamItem() { | |||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class); | |||||
GatewayParamParser<Object> parser = new GatewayParamParser<>(itemParser); | |||||
// Create a fake request. | |||||
Object request = new Object(); | |||||
// Prepare gateway rules. | |||||
Set<GatewayFlowRule> rules = new HashSet<>(); | |||||
String routeId1 = "my_test_route_A"; | |||||
rules.add(new GatewayFlowRule(routeId1) | |||||
.setCount(5) | |||||
.setIntervalSec(1) | |||||
); | |||||
GatewayRuleManager.loadRules(rules); | |||||
Object[] params = parser.parseParameterFor(routeId1, request, routeIdPredicate); | |||||
assertThat(params.length).isZero(); | |||||
} | |||||
@Test | |||||
public void testParseParametersWithItems() { | |||||
RequestItemParser<Object> itemParser = mock(RequestItemParser.class); | |||||
GatewayParamParser<Object> paramParser = new GatewayParamParser<>(itemParser); | |||||
// Create a fake request. | |||||
Object request = new Object(); | |||||
// Prepare gateway rules. | |||||
Set<GatewayFlowRule> rules = new HashSet<>(); | |||||
final String routeId1 = "my_test_route_A"; | |||||
final String api1 = "my_test_route_B"; | |||||
final String headerName = "X-Sentinel-Flag"; | |||||
final String paramName = "p"; | |||||
GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1) | |||||
.setCount(10) | |||||
.setIntervalSec(10); | |||||
GatewayFlowRule routeRule1 = new GatewayFlowRule(routeId1) | |||||
.setCount(2) | |||||
.setIntervalSec(2) | |||||
.setBurst(2) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) | |||||
); | |||||
GatewayFlowRule routeRule2 = new GatewayFlowRule(routeId1) | |||||
.setCount(10) | |||||
.setIntervalSec(1) | |||||
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) | |||||
.setMaxQueueingTimeoutMs(600) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) | |||||
.setFieldName(headerName) | |||||
); | |||||
GatewayFlowRule routeRule3 = new GatewayFlowRule(routeId1) | |||||
.setCount(20) | |||||
.setIntervalSec(1) | |||||
.setBurst(5) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) | |||||
.setFieldName(paramName) | |||||
); | |||||
GatewayFlowRule routeRule4 = new GatewayFlowRule(routeId1) | |||||
.setCount(120) | |||||
.setIntervalSec(10) | |||||
.setBurst(30) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) | |||||
); | |||||
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) | |||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) | |||||
.setCount(5) | |||||
.setIntervalSec(1) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM) | |||||
.setFieldName(paramName) | |||||
); | |||||
rules.add(routeRule1); | |||||
rules.add(routeRule2); | |||||
rules.add(routeRule3); | |||||
rules.add(routeRule4); | |||||
rules.add(routeRuleNoParam); | |||||
rules.add(apiRule1); | |||||
GatewayRuleManager.loadRules(rules); | |||||
final String expectedHost = "hello.test.sentinel"; | |||||
final String expectedAddress = "66.77.88.99"; | |||||
final String expectedHeaderValue1 = "Sentinel"; | |||||
final String expectedUrlParamValue1 = "17"; | |||||
mockClientHostAddress(itemParser, expectedAddress); | |||||
Map<String, String> expectedHeaders = new HashMap<String, String>() {{ | |||||
put(headerName, expectedHeaderValue1); put("Host", expectedHost); | |||||
}}; | |||||
mockHeaders(itemParser, expectedHeaders); | |||||
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1); | |||||
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); | |||||
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[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM); | |||||
assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero(); | |||||
String expectedUrlParamValue2 = "fs"; | |||||
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue2); | |||||
params = paramParser.parseParameterFor(api1, request, apiNamePredicate); | |||||
assertThat(params.length).isEqualTo(1); | |||||
assertThat(params[apiRule1.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue2); | |||||
} | |||||
private void mockClientHostAddress(/*@Mock*/ RequestItemParser parser, String address) { | |||||
when(parser.getRemoteAddress(any())).thenReturn(address); | |||||
} | |||||
private void mockHeaders(/*@Mock*/ RequestItemParser parser, Map<String, String> headerMap) { | |||||
for (Map.Entry<String, String> e : headerMap.entrySet()) { | |||||
when(parser.getHeader(any(), eq(e.getKey()))).thenReturn(e.getValue()); | |||||
} | |||||
} | |||||
private void mockUrlParams(/*@Mock*/ RequestItemParser parser, Map<String, String> paramMap) { | |||||
for (Map.Entry<String, String> e : paramMap.entrySet()) { | |||||
when(parser.getUrlParam(any(), eq(e.getKey()))).thenReturn(e.getValue()); | |||||
} | |||||
} | |||||
private void mockSingleUrlParam(/*@Mock*/ RequestItemParser parser, String key, String value) { | |||||
when(parser.getUrlParam(any(), eq(key))).thenReturn(value); | |||||
} | |||||
private void mockSingleHeader(/*@Mock*/ RequestItemParser parser, String key, String value) { | |||||
when(parser.getHeader(any(), eq(key))).thenReturn(value); | |||||
} | |||||
@Before | |||||
public void setUp() { | |||||
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>()); | |||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>()); | |||||
} | |||||
@After | |||||
public void tearDown() { | |||||
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>()); | |||||
GatewayRuleManager.loadRules(new HashSet<GatewayFlowRule>()); | |||||
} | |||||
} |
@@ -16,12 +16,12 @@ | |||||
package com.alibaba.csp.sentinel.adapter.gateway.common.rule; | package com.alibaba.csp.sentinel.adapter.gateway.common.rule; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | |||||
import java.util.Set; | import java.util.Set; | ||||
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; | ||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
@@ -62,8 +62,8 @@ public class GatewayRuleManagerTest { | |||||
rules.add(rule3); | rules.add(rule3); | ||||
GatewayRuleManager.loadRules(rules); | GatewayRuleManager.loadRules(rules); | ||||
assertTrue(FlowRuleManager.hasConfig(ahasRoute)); | |||||
assertTrue(ParamFlowRuleManager.hasRules(ahasRoute)); | |||||
List<ParamFlowRule> convertedRules = GatewayRuleManager.getConvertedParamRules(ahasRoute); | |||||
assertNotNull(convertedRules); | |||||
assertEquals(0, (int)rule2.getParamItem().getIndex()); | assertEquals(0, (int)rule2.getParamItem().getIndex()); | ||||
assertEquals(0, (int)rule3.getParamItem().getIndex()); | assertEquals(0, (int)rule3.getParamItem().getIndex()); | ||||
assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1)); | assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1)); | ||||