diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml index 9daaf897..4a361efb 100755 --- a/sentinel-adapter/pom.xml +++ b/sentinel-adapter/pom.xml @@ -18,6 +18,7 @@ sentinel-web-servlet sentinel-dubbo-adapter sentinel-apache-dubbo-adapter + sentinel-apache-dubbo3-adapter sentinel-apache-httpclient-adapter sentinel-sofa-rpc-adapter sentinel-grpc-adapter diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/README.md b/sentinel-adapter/sentinel-apache-dubbo3-adapter/README.md new file mode 100755 index 00000000..c215d4e8 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/README.md @@ -0,0 +1,75 @@ +# Sentinel Apache Dubbo Adapter (for 3.0.5+) + +> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。 + +Sentinel Dubbo Adapter provides service consumer filter and provider filter +for [Apache Dubbo](https://dubbo.apache.org/en-us/) services. + +**Note: This adapter only supports Apache Dubbo 3.0.5 and above.** + +For legacy `com.alibaba:dubbo` 2.6.x, please use `sentinel-dubbo-adapter` module instead. + +For `org.apache:dubbo` 2.7.x, please use `sentinel-apache-dubbo-adapter` module instead. + +To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`: + +```xml + + com.alibaba.csp + sentinel-apache-dubbo3-adapter + x.y.z + +``` + +The Sentinel filters are **enabled by default**. Once you add the dependency, +the Dubbo services and methods will become protected resources in Sentinel, +which can leverage Sentinel's flow control and guard ability when rules are configured. +Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo). + +If you don't want the filters enabled, you can manually disable them. For example: + +```xml + + + +``` + +For more details of Dubbo filter, see [here](http://dubbo.apache.org/en-us/docs/dev/impls/filter.html). + +## Dubbo resources + +The resource for Dubbo services has two granularities: service interface and service method. + +- Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` +- Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` + +## Flow control based on caller + +In many circumstances, it's also significant to control traffic flow based on the **caller**. +For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider. +If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name). + +Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`), +and will bring the caller's name when doing resource protection. +If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers. +If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. + +> Note: Dubbo consumer does not provide its Dubbo application name when doing RPC, +> so developers should manually put the application name into *attachment* at consumer side, +> then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) +> where consumer can carry application name information to provider automatically. +> If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, +> developers can manually put the application name into attachment with the key `dubboApplication`. +> +> Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser` +> implementation to `DubboAdapterGlobalConfig`. + +## Global fallback + +Sentinel Dubbo Adapter supports global fallback configuration. +The global fallback will handle exceptions and give replacement result when blocked by +flow control, degrade or system load protection. You can implement your own `DubboFallback` interface +and then register to `DubboAdapterGlobalConfig`. +If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result. + +Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services. \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/pom.xml b/sentinel-adapter/sentinel-apache-dubbo3-adapter/pom.xml new file mode 100644 index 00000000..dbebaea8 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/pom.xml @@ -0,0 +1,50 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.8.4 + + 4.0.0 + + sentinel-apache-dubbo3-adapter + jar + + + 1.8 + 1.8 + 3.0.9 + + + + + com.alibaba.csp + sentinel-core + + + org.apache.dubbo + dubbo + ${apache.dubbo.version} + provided + + + + junit + junit + test + + + + org.mockito + mockito-core + test + + + com.alibaba + fastjson + test + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/BaseSentinelDubboFilter.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/BaseSentinelDubboFilter.java new file mode 100644 index 00000000..76ed1c54 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/BaseSentinelDubboFilter.java @@ -0,0 +1,48 @@ +/* + * 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.dubbo3; + + +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +/** + * Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. + * + * @author Zechao Zheng + */ +public abstract class BaseSentinelDubboFilter { + + + /** + * Get method name of dubbo rpc + * + * @param invoker + * @param invocation + * @return + */ + abstract String getMethodName(Invoker invoker, Invocation invocation, String prefix); + + /** + * Get interface name of dubbo rpc + * + * @param invoker + * @return + */ + abstract String getInterfaceName(Invoker invoker, String prefix); + + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilter.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilter.java new file mode 100644 index 00000000..b3f92575 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilter.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.adapter.dubbo3; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; +import org.apache.dubbo.rpc.model.ApplicationModel; + +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) +public class DubboAppContextFilter implements ClusterFilter { + + private final String applicationName; + + public DubboAppContextFilter(ApplicationModel applicationModel) { + this.applicationName = applicationModel.tryGetApplicationName(); + } + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (applicationName != null && !applicationName.isEmpty()) { + invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, applicationName); + } + return invoker.invoke(invocation); + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtils.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtils.java new file mode 100644 index 00000000..b0e57edb --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtils.java @@ -0,0 +1,109 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.util.StringUtil; + +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcContext; + +import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY; + +/** + * @author Eric Zhao + */ +public final class DubboUtils { + + public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication"; + + public static String getApplication(Invocation invocation, String defaultValue) { + if (invocation == null || invocation.getAttachments() == null) { + throw new IllegalArgumentException("Bad invocation instance"); + } + // 1. try to get application from dubbo context + String remoteApplication = invocation.getAttachment(REMOTE_APPLICATION_KEY); + if (StringUtils.isEmpty(remoteApplication)) { + remoteApplication = RpcContext.getServerAttachment().getAttachment(REMOTE_APPLICATION_KEY); + } + if (StringUtils.isNotEmpty(remoteApplication)) { + return remoteApplication; + } + // 2. fallback to sentinel application + return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); + } + + public static String getMethodResourceName(Invoker invoker, Invocation invocation){ + return getMethodResourceName(invoker, invocation, false); + } + + public static String getMethodResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { + StringBuilder buf = new StringBuilder(64); + String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); + buf.append(interfaceResource) + .append(":") + .append(invocation.getMethodName()) + .append("("); + boolean isFirst = true; + for (Class clazz : invocation.getParameterTypes()) { + if (!isFirst) { + buf.append(","); + } + buf.append(clazz.getName()); + isFirst = false; + } + buf.append(")"); + return buf.toString(); + } + + public static String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { + if (StringUtil.isNotBlank(prefix)) { + return new StringBuilder(64) + .append(prefix) + .append(getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) + .toString(); + } else { + return getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); + } + } + + + public static String getInterfaceName(Invoker invoker) { + return getInterfaceName(invoker, false); + } + + public static String getInterfaceName(Invoker invoker, Boolean useGroupAndVersion) { + StringBuilder buf = new StringBuilder(64); + return useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); + } + + public static String getInterfaceName(Invoker invoker, String prefix) { + if (StringUtil.isNotBlank(prefix)) { + return new StringBuilder(64) + .append(prefix) + .append(getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) + .toString(); + } else { + return getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); + } + } + + + private DubboUtils() { + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilter.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilter.java new file mode 100644 index 00000000..34306bde --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilter.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.adapter.dubbo3; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.*; +import org.apache.dubbo.rpc.cluster.filter.ClusterFilter; +import org.apache.dubbo.rpc.support.RpcUtils; + +import java.util.LinkedList; +import java.util.Optional; + +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: + *

+ * <dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>
+ * 
+ * + * @author Carpenter Lee + * @author Eric Zhao + * @author Lin Liang + */ +@Activate(group = CONSUMER) +public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter implements ClusterFilter { + + public SentinelDubboConsumerFilter() { + RecordLog.info("Sentinel Apache Dubbo3 consumer filter initialized"); + } + + @Override + String getMethodName(Invoker invoker, Invocation invocation, String prefix) { + return DubboUtils.getMethodResourceName(invoker, invocation, prefix); + } + + @Override + String getInterfaceName(Invoker invoker, String prefix) { + return DubboUtils.getInterfaceName(invoker, prefix); + } + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); + if (InvokeMode.SYNC == invokeMode) { + return syncInvoke(invoker, invocation); + } else { + return asyncInvoke(invoker, invocation); + } + } + + private Result syncInvoke(Invoker invoker, Invocation invocation) { + Entry interfaceEntry = null; + Entry methodEntry = null; + String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); + try { + interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, + invocation.getArguments()); + Result result = invoker.invoke(invocation); + if (result.hasException()) { + Tracer.traceEntry(result.getException(), interfaceEntry); + Tracer.traceEntry(result.getException(), methodEntry); + } + return result; + } catch (BlockException e) { + return DubboAdapterGlobalConfig.getConsumerFallback().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(); + } + } + } + + private Result asyncInvoke(Invoker invoker, Invocation invocation) { + LinkedList queue = new LinkedList<>(); + String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); + try { + queue.push(new EntryHolder( + SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); + queue.push(new EntryHolder( + SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, + EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); + Result result = invoker.invoke(invocation); + result.whenCompleteWithContext((r, throwable) -> { + Throwable error = throwable; + if (error == null) { + error = Optional.ofNullable(r).map(Result::getException).orElse(null); + } + while (!queue.isEmpty()) { + EntryHolder holder = queue.pop(); + Tracer.traceEntry(error, holder.entry); + exitEntry(holder); + } + }); + return result; + } catch (BlockException e) { + while (!queue.isEmpty()) { + exitEntry(queue.pop()); + } + return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); + } + } + + static class EntryHolder { + + final private Entry entry; + final private Object[] params; + + public EntryHolder(Entry entry, Object[] params) { + this.entry = entry; + this.params = params; + } + } + + private void exitEntry(EntryHolder holder) { + if (holder.params != null) { + holder.entry.exit(1, holder.params); + } else { + holder.entry.exit(); + } + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilter.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilter.java new file mode 100644 index 00000000..6d73afed --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilter.java @@ -0,0 +1,105 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; + +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; + +/** + *

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"/>
+ * 
+ * + * @author Carpenter Lee + * @author Eric Zhao + */ +@Activate(group = PROVIDER) +public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter implements Filter { + + public SentinelDubboProviderFilter() { + RecordLog.info("Sentinel Apache Dubbo3 provider filter initialized"); + } + + @Override + String getMethodName(Invoker invoker, Invocation invocation, String prefix) { + return DubboUtils.getMethodResourceName(invoker, invocation, prefix); + } + + @Override + String getInterfaceName(Invoker invoker, String prefix) { + return DubboUtils.getInterfaceName(invoker, prefix); + } + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + // Get origin caller. + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); + if (null == origin) { + origin = ""; + } + Entry interfaceEntry = null; + Entry methodEntry = null; + String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); + try { + // 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(methodResourceName, origin); + interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, + invocation.getArguments()); + Result result = invoker.invoke(invocation); + if (result.hasException()) { + Tracer.traceEntry(result.getException(), interfaceEntry); + Tracer.traceEntry(result.getException(), methodEntry); + } + return result; + } catch (BlockException e) { + return DubboAdapterGlobalConfig.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-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/config/DubboAdapterGlobalConfig.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/config/DubboAdapterGlobalConfig.java new file mode 100644 index 00000000..87f7da13 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/config/DubboAdapterGlobalConfig.java @@ -0,0 +1,116 @@ +/* + * 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.dubbo3.config; + +import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DefaultDubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo3.origin.DefaultDubboOriginParser; +import com.alibaba.csp.sentinel.adapter.dubbo3.origin.DubboOriginParser; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + *

+ * Responsible for dubbo service provider, consumer attribute configuration + *

+ * + * @author lianglin + * @since 1.7.0 + */ +public final class DubboAdapterGlobalConfig { + + private static final String TRUE_STR = "true"; + + public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; + public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; + public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "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"; + + private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); + private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); + private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); + + public static boolean isUsePrefix() { + return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); + } + + public static String getDubboProviderResNamePrefixKey() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); + return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; + } + return null; + } + + public static String getDubboConsumerResNamePrefixKey() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); + 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 DubboFallback getConsumerFallback() { + return consumerFallback; + } + + public static void setConsumerFallback(DubboFallback consumerFallback) { + AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); + DubboAdapterGlobalConfig.consumerFallback = consumerFallback; + } + + public static DubboFallback getProviderFallback() { + return providerFallback; + } + + public static void setProviderFallback(DubboFallback providerFallback) { + AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); + DubboAdapterGlobalConfig.providerFallback = providerFallback; + } + + /** + * Get the origin parser of Dubbo adapter. + * + * @return the origin parser + * @since 1.8.0 + */ + public static DubboOriginParser getOriginParser() { + return originParser; + } + + /** + * Set the origin parser of Dubbo adapter. + * + * @param originParser the origin parser + * @since 1.8.0 + */ + public static void setOriginParser(DubboOriginParser originParser) { + AssertUtil.notNull(originParser, "originParser cannot be null"); + DubboAdapterGlobalConfig.originParser = originParser; + } + + private DubboAdapterGlobalConfig() {} + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DefaultDubboFallback.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DefaultDubboFallback.java new file mode 100644 index 00000000..1d72308d --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DefaultDubboFallback.java @@ -0,0 +1,35 @@ +/* + * 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.dubbo3.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import org.apache.dubbo.rpc.AsyncRpcResult; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; + +/** + * @author Eric Zhao + */ +public class DefaultDubboFallback implements DubboFallback { + + @Override + public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { + // Just wrap the exception. + return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation); + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallback.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallback.java new file mode 100644 index 00000000..7545fae3 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallback.java @@ -0,0 +1,41 @@ +/* + * 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.dubbo3.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; + +/** + * Fallback handler for Dubbo services. + * + * @author Eric Zhao + */ +@FunctionalInterface +public interface DubboFallback { + + /** + * Handle the block exception and provide fallback result. + * + * @param invoker Dubbo invoker + * @param invocation Dubbo invocation + * @param ex block exception + * @return fallback result + */ + Result handle(Invoker invoker, Invocation invocation, BlockException ex); +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistry.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistry.java new file mode 100644 index 00000000..5879c14e --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistry.java @@ -0,0 +1,46 @@ +/* + * 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.dubbo3.fallback; + +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; + +/** + *

Global fallback registry for Dubbo.

+ * + * @author Eric Zhao + * @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0. + */ +@Deprecated +public final class DubboFallbackRegistry { + + public static DubboFallback getConsumerFallback() { + return DubboAdapterGlobalConfig.getConsumerFallback(); + } + + public static void setConsumerFallback(DubboFallback consumerFallback) { + DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); + } + + public static DubboFallback getProviderFallback() { + return DubboAdapterGlobalConfig.getProviderFallback(); + } + + public static void setProviderFallback(DubboFallback providerFallback) { + DubboAdapterGlobalConfig.setProviderFallback(providerFallback); + } + + private DubboFallbackRegistry() {} +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DefaultDubboOriginParser.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DefaultDubboOriginParser.java new file mode 100644 index 00000000..d2c9474c --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DefaultDubboOriginParser.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 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.dubbo3.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo3.DubboUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +/** + * Default Dubbo origin parser. + * + * @author jingzian + */ +public class DefaultDubboOriginParser implements DubboOriginParser { + + @Override + public String parse(Invoker invoker, Invocation invocation) { + return DubboUtils.getApplication(invocation, ""); + } + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginParser.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginParser.java new file mode 100644 index 00000000..3ed312a4 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 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.dubbo3.origin; + +import com.alibaba.csp.sentinel.context.Context; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +/** + * Customized origin parser for Dubbo provider filter.{@link Context#getOrigin()} + * + * @author jingzian + */ +public interface DubboOriginParser { + + /** + * Parses the origin (caller) from Dubbo invocation. + * + * @param invoker Dubbo invoker + * @param invocation Dubbo invocation + * @return the parsed origin + */ + String parse(Invoker invoker, Invocation invocation); + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter new file mode 100755 index 00000000..5f26ef3c --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter @@ -0,0 +1 @@ +sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.filter.ClusterFilter b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.filter.ClusterFilter new file mode 100644 index 00000000..fc71a515 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.filter.ClusterFilter @@ -0,0 +1,2 @@ +dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter +sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java new file mode 100644 index 00000000..e4642cc9 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java @@ -0,0 +1,75 @@ +/* + * 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; + +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.adapter.dubbo3.fallback.DefaultDubboFallback; +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.rpc.RpcContext; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +/** + * 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 + * @author lianglin + */ +public class BaseTest { + + + /** + * Clean up resources for context, clusterNodeMap, processorSlotChainMap + */ + public void cleanUpAll() { + try { + clearDubboContext(); + cleanUpCstContext(); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + private void cleanUpCstContext() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + ClusterBuilderSlot.getClusterNodeMap().clear(); + CtSph.resetChainMap(); + Method method = ContextUtil.class.getDeclaredMethod("resetContextMap"); + method.setAccessible(true); + method.invoke(null, null); + ContextUtil.exit(); + FlowRuleManager.loadRules(new ArrayList<>()); + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + private void clearDubboContext() { + SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); + RpcContext.removeContext(); + + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java new file mode 100644 index 00000000..b7f1912b --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.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; + +import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; +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.RpcInvocation; + +import java.lang.reflect.Method; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author lianglin + */ +public class DubboTestUtil { + + + public static Class DEFAULT_TEST_SERVICE = DemoService.class; + public static Method DEFAULT_TEST_METHOD_ONE = DEFAULT_TEST_SERVICE.getMethods()[0]; + public static Method DEFAULT_TEST_METHOD_TWO = DEFAULT_TEST_SERVICE.getMethods()[1]; + + public static Invoker getMockInvoker(URL url, Class cls) { + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(cls); + return invoker; + } + + public static Invoker getDefaultMockInvoker() { + return getMockInvoker(getDefaultTestURL(), DEFAULT_TEST_SERVICE); + } + + public static Invocation getMockInvocation(Method method) { + Invocation invocation = mock(RpcInvocation.class); + when(invocation.getMethodName()).thenReturn(method.getName()); + when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); + return invocation; + } + + public static Invocation getDefaultMockInvocationOne() { + Invocation invocation = mock(RpcInvocation.class); + when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_ONE.getName()); + when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_ONE.getParameterTypes()); + return invocation; + } + + public static Invocation getDefaultMockInvocationTwo() { + Invocation invocation = mock(RpcInvocation.class); + when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_TWO.getName()); + when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_TWO.getParameterTypes()); + return invocation; + } + + public static URL getDefaultTestURL() { + 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, DEFAULT_TEST_SERVICE.getName()); + return url; + } + + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilterTest.java new file mode 100644 index 00000000..89ff76e2 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboAppContextFilterTest.java @@ -0,0 +1,82 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.BaseTest; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author cdfive + */ +public class DubboAppContextFilterTest extends BaseTest { + + @Before + public void setUp() { + cleanUpAll(); + } + + @After + public void cleanUp() { + cleanUpAll(); + } + + @Test + public void testInvokeApplicationKey() { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + URL url = URL.valueOf("test://test:111/test?application=serviceA"); + when(invoker.getUrl()).thenReturn(url); + + ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication(); + applicationModel.getApplicationConfigManager().setApplication(new ApplicationConfig("serviceA")); + DubboAppContextFilter filter = new DubboAppContextFilter(applicationModel); + filter.invoke(invoker, invocation); + verify(invoker).invoke(invocation); + + verify(invocation).setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "serviceA"); + applicationModel.destroy(); + } + + @Test + public void testInvokeNullApplicationKey() { + Invoker invoker = mock(Invoker.class); + Invocation invocation = mock(Invocation.class); + URL url = URL.valueOf("test://test:111/test?application="); + when(invoker.getUrl()).thenReturn(url); + + ApplicationModel applicationModel = FrameworkModel.defaultModel().newApplication(); + DubboAppContextFilter filter = new DubboAppContextFilter(applicationModel); + filter.invoke(invoker, invocation); + verify(invoker).invoke(invocation); + + verify(invocation, times(0)).setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "serviceA"); + applicationModel.destroy(); + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtilsTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtilsTest.java new file mode 100644 index 00000000..70124ccf --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/DubboUtilsTest.java @@ -0,0 +1,206 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.adapter.dubbo3.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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +/** + * @author cdfive + */ +public class DubboUtilsTest { + + @Before + public void setUp() { + SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + } + + + @After + public void tearDown() { + SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.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"); + + String application = DubboUtils.getApplication(invocation, ""); + verify(invocation).getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, ""); + + assertEquals("consumerA", application); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetApplicationNoAttachments() { + Invocation invocation = mock(Invocation.class); + when(invocation.getAttachments()).thenReturn(null); + when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) + .thenReturn("consumerA"); + + DubboUtils.getApplication(invocation, ""); + + fail("No attachments in invocation, IllegalArgumentException should be thrown!"); + } + + @Test + 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.getDeclaredMethod("sayHello", String.class, int.class); + when(invocation.getMethodName()).thenReturn(method.getName()); + when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); + + String resourceName = DubboUtils.getMethodResourceName(invoker, invocation); + + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.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.getMethodResourceName(invoker, invocation, true); + + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.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.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); + + + //test with custom prefix + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:sayHello(java.lang.String,int)", resourceName); + + } + + @Test + public void testGetInterfaceName() { + + 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()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", DubboUtils.getInterfaceName(invoker)); + + } + + @Test + public void testGetInterfaceNameWithGroupAndVersion() throws NoSuchMethodException { + + 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()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true"); + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService:1.0.0:grp1", DubboUtils.getInterfaceName(invoker, true)); + } + + @Test + public void testGetInterfaceNameWithPrefix() throws NoSuchMethodException { + 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()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + + //test with default prefix + String resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); + + + //test with custom prefix + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService", resourceName); + + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilterTest.java new file mode 100644 index 00000000..a91d67d4 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboConsumerFilterTest.java @@ -0,0 +1,391 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.BaseTest; +import com.alibaba.csp.sentinel.DubboTestUtil; +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.DefaultNode; +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.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.*; +import org.apache.dubbo.rpc.support.RpcUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.*; + +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.*; +import static org.mockito.Mockito.*; + +/** + * @author cdfive + * @author lianglin + */ +public class SentinelDubboConsumerFilterTest extends BaseTest { + + private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); + + @Before + public void setUp() { + cleanUpAll(); + initFallback(); + } + + @After + public void destroy() { + cleanUpAll(); + } + + @Test + public void testInterfaceLevelFollowControlAsync() throws InterruptedException { + + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initFlowRule(DubboUtils.getInterfaceName(invoker)); + + Result result1 = invokeDubboRpc(false, invoker, invocation); + assertEquals("normal", result1.getValue()); + + // should fallback because the qps > 1 + Result result2 = invokeDubboRpc(false, invoker, invocation); + assertEquals("fallback", result2.getValue()); + + // sleeping 1000 ms to reset qps + Thread.sleep(1000); + Result result3 = invokeDubboRpc(false, invoker, invocation); + assertEquals("normal", result3.getValue()); + + verifyInvocationStructureForCallFinish(invoker, invocation); + } + + @Test + public void testDegradeAsync() throws InterruptedException { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initDegradeRule(DubboUtils.getInterfaceName(invoker)); + + Result result = invokeDubboRpc(false, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); + assertEquals("normal", result.getValue()); + + // inc the clusterNode's exception to trigger the fallback + for (int i = 0; i < 5; i++) { + invokeDubboRpc(true, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); + } + + Result result2 = invokeDubboRpc(false, invoker, invocation); + assertEquals("fallback", result2.getValue()); + + // sleeping 1000 ms to reset exception + Thread.sleep(1000); + Result result3 = invokeDubboRpc(false, invoker, invocation); + assertEquals("normal", result3.getValue()); + + Context context = ContextUtil.getContext(); + assertNull(context); + } + + @Test + public void testDegradeSync() throws InterruptedException { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + initDegradeRule(DubboUtils.getInterfaceName(invoker)); + + Result result = invokeDubboRpc(false, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); + assertEquals("normal", result.getValue()); + + // inc the clusterNode's exception to trigger the fallback + for (int i = 0; i < 5; i++) { + invokeDubboRpc(true, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); + } + + Result result2 = invokeDubboRpc(false, invoker, invocation); + assertEquals("fallback", result2.getValue()); + + // sleeping 1000 ms to reset exception + Thread.sleep(1000); + Result result3 = invokeDubboRpc(false, invoker, invocation); + assertEquals("normal", result3.getValue()); + + Context context = ContextUtil.getContext(); + assertNull(context); + } + + @Test + public void testMethodFlowControlAsync() { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initFlowRule(consumerFilter.getMethodName(invoker, invocation, null)); + invokeDubboRpc(false, invoker, invocation); + invokeDubboRpc(false, invoker, invocation); + + Invocation invocation2 = DubboTestUtil.getDefaultMockInvocationTwo(); + Result result2 = invokeDubboRpc(false, invoker, invocation2); + verifyInvocationStructureForCallFinish(invoker, invocation2); + assertEquals("normal", result2.getValue()); + + // the method of invocation should be blocked + Result fallback = invokeDubboRpc(false, invoker, invocation); + assertEquals("fallback", fallback.getValue()); + verifyInvocationStructureForCallFinish(invoker, invocation); + + } + + @Test + public void testInvokeAsync() { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + + 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; + }); + consumerFilter.invoke(invoker, invocation); + verify(invoker).invoke(invocation); + + Context context = ContextUtil.getContext(); + assertNotNull(context); + } + + @Test + public void testInvokeSync() { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + + 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; + }); + + consumerFilter.invoke(invoker, invocation); + verify(invoker).invoke(invocation); + + Context context = ContextUtil.getContext(); + assertNull(context); + } + + /** + * Simply verify invocation structure in memory: + * EntranceNode(defaultContextName) + * --InterfaceNode(interfaceName) + * ----MethodNode(resourceName) + */ + 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 = consumerFilter.getMethodName(invoker, invocation, null); + 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(1, childList.size()); + DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); + ResourceWrapper interfaceResource = interfaceNode.getId(); + + assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); + assertSame(EntryType.OUT, interfaceResource.getEntryType()); + + // As SphU.entry(resourceName, EntryType.OUT); + childList = interfaceNode.getChildList(); + assertEquals(1, childList.size()); + DefaultNode methodNode = getNode(resourceName, entranceNode); + ResourceWrapper methodResource = methodNode.getId(); + assertEquals(resourceName, methodResource.getName()); + assertSame(EntryType.OUT, methodResource.getEntryType()); + + // Verify curEntry + Entry curEntry = context.getCurEntry(); + assertSame(methodNode, curEntry.getCurNode()); + assertSame(interfaceNode, curEntry.getLastNode()); + assertNull(curEntry.getOriginNode());// As context origin is not "", no originNode should be created in 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 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 = consumerFilter.getMethodName(invoker, invocation, null); + 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(DubboUtils.getInterfaceName(invoker), entranceNode); + ResourceWrapper interfaceResource = interfaceNode.getId(); + assertEquals(DubboUtils.getInterfaceName(invoker), 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(Invoker invoker, Invocation invocation) { + Context context = ContextUtil.getContext(); + assertNull(context); + String methodResourceName = consumerFilter.getMethodName(invoker, invocation, null); + Entry[] entries = (Entry[]) RpcContext.getContext().get(methodResourceName); + assertNull(entries); + } + + private 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; + } + + private 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); + } + + private 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); + } + + private void initFallback() { + DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> { + // boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); + return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); + }); + } + + private Result invokeDubboRpc(boolean exception, Invoker invoker, Invocation invocation) { + Result result = null; + InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); + if (InvokeMode.SYNC == invokeMode) { + result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal"); + } else { + result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) + : AsyncRpcResult.newDefaultAsyncResult("normal", invocation); + } + when(invoker.invoke(invocation)).thenReturn(result); + return consumerFilter.invoke(invoker, invocation); + } + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilterTest.java new file mode 100644 index 00000000..d90cdc45 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/SentinelDubboProviderFilterTest.java @@ -0,0 +1,153 @@ +/* + * 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.dubbo3; + +import com.alibaba.csp.sentinel.BaseTest; +import com.alibaba.csp.sentinel.DubboTestUtil; +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.DefaultNode; +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; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author cdfive + * @author lianglin + */ +public class SentinelDubboProviderFilterTest extends BaseTest { + + + private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter(); + + + @Before + public void setUp() { + cleanUpAll(); + } + + @After + public void destroy() { + cleanUpAll(); + } + + @Test + public void testInvoke() { + + final String originApplication = "consumerA"; + + URL url = DubboTestUtil.getDefaultTestURL(); + url = url.addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); + Invoker invoker = DubboTestUtil.getMockInvoker(url, DemoService.class); + + Invocation invocation = DubboTestUtil.getMockInvocation(DemoService.class.getMethods()[0]); + when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) + .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; + }); + + filter.invoke(invoker, invocation); + verify(invoker).invoke(invocation); + + Context context = ContextUtil.getContext(); + assertNull(context); + } + + /** + * Simply verify invocation structure in memory: + * EntranceNode(methodResourceName) + * --InterfaceNode(interfaceName) + * ----MethodNode(methodResourceName) + */ + private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) { + Context context = ContextUtil.getContext(); + assertNotNull(context); + + // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter + String methodResourceName = filter.getMethodName(invoker, invocation, null); + assertEquals(methodResourceName, context.getName()); + assertEquals(originApplication, context.getOrigin()); + + DefaultNode entranceNode = context.getEntranceNode(); + ResourceWrapper entranceResource = entranceNode.getId(); + assertEquals(methodResourceName, entranceResource.getName()); + 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(filter.getInterfaceName(invoker, null), interfaceResource.getName()); + assertSame(EntryType.IN, interfaceResource.getEntryType()); + + // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); + childList = interfaceNode.getChildList(); + assertEquals(1, childList.size()); + DefaultNode methodNode = (DefaultNode) childList.iterator().next(); + ResourceWrapper methodResource = methodNode.getId(); + assertEquals(methodResourceName, methodResource.getName()); + assertSame(EntryType.IN, methodResource.getEntryType()); + + // Verify curEntry + Entry curEntry = context.getCurEntry(); + assertSame(methodNode, curEntry.getCurNode()); + assertSame(interfaceNode, curEntry.getLastNode()); + assertNotNull(curEntry.getOriginNode());// As context origin is not "", originNode should be created + + // Verify clusterNode + ClusterNode methodClusterNode = methodNode.getClusterNode(); + ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); + assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode + + // As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode + Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); + assertEquals(1, methodOriginCountMap.size()); + assertTrue(methodOriginCountMap.containsKey(originApplication)); + + Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); + assertEquals(1, interfaceOriginCountMap.size()); + assertTrue(interfaceOriginCountMap.containsKey(originApplication)); + } + + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistryTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistryTest.java new file mode 100644 index 00000000..67286f4d --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/fallback/DubboFallbackRegistryTest.java @@ -0,0 +1,70 @@ +/* + * 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.dubbo3.fallback; + +import com.alibaba.csp.sentinel.DubboTestUtil; +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.slots.block.BlockException; +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.RpcInvocation; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; + +/** + * @author Eric Zhao + */ +public class DubboFallbackRegistryTest { + + @Before + public void setUp() { + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); + } + + @After + public void tearDown() { + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); + } + + @Test + public void testDefaultFallback() { + // Test for default fallback. + BlockException ex = new FlowException("xxx"); + Result result = new DefaultDubboFallback().handle(null, mock(RpcInvocation.class), ex); + Assert.assertTrue("The result should carry exception", result.hasException()); + Assert.assertTrue(BlockException.isBlockException(result.getException())); + Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName())); + } + + @Test + public void testCustomFallback() { + BlockException ex = new FlowException("xxx"); + DubboAdapterGlobalConfig.setConsumerFallback( + (invoker, invocation, e) -> AsyncRpcResult + .newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); + Result result = DubboAdapterGlobalConfig.getConsumerFallback() + .handle(null, mock(RpcInvocation.class), ex); + Assert.assertFalse("The invocation should not fail", result.hasException()); + Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginRegistryTest.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginRegistryTest.java new file mode 100644 index 00000000..10b24557 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/origin/DubboOriginRegistryTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2020 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.dubbo3.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo3.DubboUtils; +import com.alibaba.csp.sentinel.adapter.dubbo3.config.DubboAdapterGlobalConfig; +import com.alibaba.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author tiecheng + */ +public class DubboOriginRegistryTest { + + @After + public void cleanUp() { + DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); + } + + @Test(expected = IllegalArgumentException.class) + public void testDefaultOriginParserFail() { + DubboAdapterGlobalConfig.getOriginParser().parse(null, null); + } + + @Test + public void testDefaultOriginParserSuccess() { + RpcInvocation invocation = new RpcInvocation(); + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName, origin); + } + + @Test + public void testCustomOriginParser() { + DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { + @Override + public String parse(Invoker invoker, Invocation invocation) { + return invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "default") + "_" + invocation + .getMethodName(); + } + }); + + RpcInvocation invocation = new RpcInvocation(); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals("default_null", origin); + + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_null", origin); + + invocation.setMethodName("hello"); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_hello", origin); + } + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/DemoService.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/DemoService.java new file mode 100644 index 00000000..abc4c351 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/DemoService.java @@ -0,0 +1,24 @@ +/* + * 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.dubbo3.provider; + +/** + * @author leyou + */ +public interface DemoService { + String sayHello(String name, int n); + String sayHi(String name,int n); +} diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/impl/DemoServiceImpl.java b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/impl/DemoServiceImpl.java new file mode 100644 index 00000000..54cdf1c2 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo3/provider/impl/DemoServiceImpl.java @@ -0,0 +1,32 @@ +/* + * 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.dubbo3.provider.impl; + +import com.alibaba.csp.sentinel.adapter.dubbo3.provider.DemoService; + +/** + * @author leyou + */ +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-apache-dubbo3-adapter/src/test/resources/spring-dubbo-consumer-filter.xml b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-consumer-filter.xml new file mode 100755 index 00000000..69acd68c --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-consumer-filter.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-provider-filter.xml b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-provider-filter.xml new file mode 100755 index 00000000..b80f486f --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo3-adapter/src/test/resources/spring-dubbo-provider-filter.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file