From ae0b8a5c749649eac8f0f5386380fd9f40dc1391 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sat, 11 May 2019 22:22:15 +0800 Subject: [PATCH] Refactor API gateway common module to separate converted rules from other rule managers - 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 --- .../pom.xml | 10 + .../common/SentinelGatewayConstants.java | 1 + .../common/param/GatewayParamParser.java | 9 +- .../common/rule/GatewayRuleConverter.java | 11 + .../common/rule/GatewayRuleManager.java | 116 ++++++++-- .../gateway/common/slot/GatewayFlowSlot.java | 75 +++++++ .../common/slot/GatewaySlotChainBuilder.java | 57 +++++ ...ba.csp.sentinel.slotchain.SlotChainBuilder | 1 + .../common/param/GatewayParamParserTest.java | 205 ++++++++++++++++++ .../common/rule/GatewayRuleManagerTest.java | 8 +- 10 files changed, 464 insertions(+), 29 deletions(-) create mode 100644 sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java create mode 100644 sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java create mode 100644 sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder create mode 100644 sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml index c5def861..4b702b43 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml @@ -37,5 +37,15 @@ junit test + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + \ 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/SentinelGatewayConstants.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/SentinelGatewayConstants.java index 4a2c6ce3..2c56e6b7 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 @@ -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_NOT_MATCH_PARAM = "$$not_match"; + 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/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 ee3dfaaf..fe51c4c5 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 @@ -53,10 +53,13 @@ public class GatewayParamParser { } Set gatewayRules = new HashSet<>(); Set predSet = new HashSet<>(); + boolean hasNonParamRule = false; for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) { if (rule.getParamItem() != null) { gatewayRules.add(rule); predSet.add(rulePredicate.test(rule)); + } else { + hasNonParamRule = true; } } if (gatewayRules.isEmpty()) { @@ -65,13 +68,17 @@ public class GatewayParamParser { if (predSet.size() != 1 || predSet.contains(false)) { 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) { GatewayParamFlowItem paramItem = rule.getParamItem(); int idx = paramItem.getIndex(); String param = parseInternal(paramItem, request); arr[idx] = param; } + if (hasNonParamRule) { + arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM; + } return arr; } 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 e2b091ba..f5531fa2 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 @@ -32,6 +32,17 @@ final class GatewayRuleConverter { .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 * parameter index to {@link GatewayParamFlowItem} of the rule. 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 f0ca5fd7..e4880bed 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 @@ -29,10 +29,9 @@ 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.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.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.StringUtil; @@ -42,7 +41,12 @@ import com.alibaba.csp.sentinel.util.StringUtil; */ public final class GatewayRuleManager { - private static final Map> RULE_MAP = new ConcurrentHashMap<>(); + /** + * Gateway flow rule map: (resource, [rules...]) + */ + private static final Map> GATEWAY_RULE_MAP = new ConcurrentHashMap<>(); + + private static final Map> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>(); private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); @@ -74,46 +78,69 @@ public final class GatewayRuleManager { public static Set getRules() { Set rules = new HashSet<>(); - for (Set ruleSet : RULE_MAP.values()) { + for (Set ruleSet : GATEWAY_RULE_MAP.values()) { rules.addAll(ruleSet); } return rules; } public static Set getRulesForResource(String resourceName) { - AssertUtil.assertNotBlank(resourceName, "resourceName cannot be blank"); - Set set = RULE_MAP.get(resourceName); + if (StringUtil.isBlank(resourceName)) { + return new HashSet<>(); + } + Set set = GATEWAY_RULE_MAP.get(resourceName); if (set == null) { return new HashSet<>(); } return new HashSet<>(set); } + /** + *

Get all converted parameter rules.

+ *

Note: caller SHOULD NOT modify the list and rules.

+ * + * @param resourceName valid resource name + * @return converted parameter rules + */ + public static List getConvertedParamRules(String resourceName) { + if (StringUtil.isBlank(resourceName)) { + return new ArrayList<>(); + } + return CONVERTED_PARAM_RULE_MAP.get(resourceName); + } + private static final class GatewayRulePropertyListener implements PropertyListener> { @Override public void configUpdate(Set conf) { applyGatewayRuleInternal(conf); - RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + RULE_MAP); + RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP); } @Override public void configLoad(Set 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 idxMap, String resourceName) { + // Prepare index map. + if (!idxMap.containsKey(resourceName)) { + idxMap.put(resourceName, 0); + } + return idxMap.get(resourceName); } private synchronized void applyGatewayRuleInternal(Set conf) { if (conf == null || conf.isEmpty()) { - FlowRuleManager.loadRules(new ArrayList()); - ParamFlowRuleManager.loadRules(new ArrayList()); - RULE_MAP.clear(); + applyToConvertedParamMap(new HashSet()); + GATEWAY_RULE_MAP.clear(); return; } Map> gatewayRuleMap = new ConcurrentHashMap<>(); Map idxMap = new HashMap<>(); - List flowRules = new ArrayList<>(); Set paramFlowRules = new HashSet<>(); + Map> noParamMap = new HashMap<>(); for (GatewayFlowRule rule : conf) { if (!isValidRule(rule)) { @@ -122,14 +149,15 @@ public final class GatewayRuleManager { } String resourceName = rule.getResource(); 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 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. if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) { idxMap.put(rule.getResource(), idx + 1); @@ -143,11 +171,51 @@ public final class GatewayRuleManager { } ruleSet.add(rule); } - FlowRuleManager.loadRules(flowRules); - ParamFlowRuleManager.loadRules(new ArrayList<>(paramFlowRules)); + // Handle non-param mode rules. + for (Map.Entry> e : noParamMap.entrySet()) { + List 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)); + } + } - RULE_MAP.clear(); - RULE_MAP.putAll(gatewayRuleMap); + applyToConvertedParamMap(paramFlowRules); + + GATEWAY_RULE_MAP.clear(); + GATEWAY_RULE_MAP.putAll(gatewayRuleMap); + } + + private void applyToConvertedParamMap(Set paramFlowRules) { + Map> 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 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); + + RecordLog.info("[GatewayRuleManager] Converted internal param rules: " + CONVERTED_PARAM_RULE_MAP); } } diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java new file mode 100644 index 00000000..15151729 --- /dev/null +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java @@ -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 { + + @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 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); + } +} diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java new file mode 100644 index 00000000..383e4653 --- /dev/null +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java @@ -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; + } +} diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder new file mode 100644 index 00000000..5f9fde56 --- /dev/null +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewaySlotChainBuilder \ No newline at end of file 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 new file mode 100644 index 00000000..9fc8dc0e --- /dev/null +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/param/GatewayParamParserTest.java @@ -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 routeIdPredicate = new Predicate() { + @Override + public boolean test(GatewayFlowRule e) { + return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID; + } + }; + private final Predicate apiNamePredicate = new Predicate() { + @Override + public boolean test(GatewayFlowRule e) { + return e.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME; + } + }; + + @Test + public void testParseParametersNoParamItem() { + RequestItemParser itemParser = mock(RequestItemParser.class); + GatewayParamParser parser = new GatewayParamParser<>(itemParser); + // Create a fake request. + Object request = new Object(); + // Prepare gateway rules. + Set 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 itemParser = mock(RequestItemParser.class); + GatewayParamParser paramParser = new GatewayParamParser<>(itemParser); + // Create a fake request. + Object request = new Object(); + + // Prepare gateway rules. + Set 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 expectedHeaders = new HashMap() {{ + 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 headerMap) { + for (Map.Entry e : headerMap.entrySet()) { + when(parser.getHeader(any(), eq(e.getKey()))).thenReturn(e.getValue()); + } + } + + private void mockUrlParams(/*@Mock*/ RequestItemParser parser, Map paramMap) { + for (Map.Entry 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()); + GatewayRuleManager.loadRules(new HashSet()); + } + + @After + public void tearDown() { + GatewayApiDefinitionManager.loadApiDefinitions(new HashSet()); + GatewayRuleManager.loadRules(new HashSet()); + } +} diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManagerTest.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManagerTest.java index ab1fcc15..fb2d0e76 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManagerTest.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/common/rule/GatewayRuleManagerTest.java @@ -16,12 +16,12 @@ package com.alibaba.csp.sentinel.adapter.gateway.common.rule; import java.util.HashSet; +import java.util.List; import java.util.Set; import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; 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.Before; @@ -62,8 +62,8 @@ public class GatewayRuleManagerTest { rules.add(rule3); GatewayRuleManager.loadRules(rules); - assertTrue(FlowRuleManager.hasConfig(ahasRoute)); - assertTrue(ParamFlowRuleManager.hasRules(ahasRoute)); + List convertedRules = GatewayRuleManager.getConvertedParamRules(ahasRoute); + assertNotNull(convertedRules); assertEquals(0, (int)rule2.getParamItem().getIndex()); assertEquals(0, (int)rule3.getParamItem().getIndex()); assertTrue(GatewayRuleManager.getRulesForResource(ahasRoute).contains(rule1));