Merge branch 'apache-3.2' into apache-3.3
# Conflicts: # dubbo-dependencies-bom/pom.xml # dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml # dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml # pom.xml
This commit is contained in:
commit
414785c05b
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check License
|
||||
uses: apache/skywalking-eyes@main
|
||||
uses: apache/skywalking-eyes@e1a02359b239bd28de3f6d35fdc870250fa513d5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: "Set up JDK 17"
|
||||
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
run: |
|
||||
./mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean install -DskipTests=true -DskipIntegrationTests=true -Dcheckstyle.skip=true -Dcheckstyle_unix.skip=true -Drat.skip=true -Dmaven.javadoc.skip=true
|
||||
- name: Check Dependencies' License
|
||||
uses: apache/skywalking-eyes/dependency@main
|
||||
uses: apache/skywalking-eyes/dependency@e1a02359b239bd28de3f6d35fdc870250fa513d5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
|
@ -79,15 +79,6 @@ jobs:
|
|||
with:
|
||||
name: "class-file"
|
||||
path: ${{ github.workspace }}/class.zip
|
||||
- name: "Pack rat file if failure"
|
||||
if: failure()
|
||||
run: 7z a ${{ github.workspace }}/rat.zip *rat.txt -r
|
||||
- name: "Upload rat file if failure"
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "rat-file"
|
||||
path: ${{ github.workspace }}/rat.zip
|
||||
- name: "Pack checkstyle file if failure"
|
||||
if: failure()
|
||||
run: 7z a ${{ github.workspace }}/checkstyle.zip *checkstyle* -r
|
||||
|
|
@ -440,8 +431,8 @@ jobs:
|
|||
- name: "Compile and run Dubbo demo for native (Linux)"
|
||||
run: |
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
nohup ./target/demo-native-provider &
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
nohup ./target/dubbo-demo-native-provider &
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
./target/demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
./target/dubbo-demo-native-consumer
|
||||
|
|
|
|||
|
|
@ -395,8 +395,8 @@ jobs:
|
|||
- name: "Compile and run Dubbo demo for native (Linux)"
|
||||
run: |
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
nohup ./target/demo-native-provider &
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
nohup ./target/dubbo-demo-native-provider &
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
./target/demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
./target/dubbo-demo-native-consumer
|
||||
|
|
|
|||
|
|
@ -395,8 +395,8 @@ jobs:
|
|||
- name: "Compile and run Dubbo demo for native (Linux)"
|
||||
run: |
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
nohup ./target/demo-native-provider &
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
nohup ./target/dubbo-demo-native-provider &
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
./target/demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
./target/dubbo-demo-native-consumer
|
||||
|
|
|
|||
|
|
@ -374,8 +374,8 @@ jobs:
|
|||
- name: "Compile and run Dubbo demo for native (Linux)"
|
||||
run: |
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
nohup ./target/demo-native-provider &
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
nohup ./target/dubbo-demo-native-provider &
|
||||
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
|
||||
./target/demo-native-consumer
|
||||
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
|
||||
./target/dubbo-demo-native-consumer
|
||||
|
|
|
|||
|
|
@ -48,8 +48,5 @@ dubbo-demo/dubbo-demo-triple/build/*
|
|||
# global registry center
|
||||
.tmp
|
||||
|
||||
dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/src/main/resources/META-INF/native-image
|
||||
dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/src/main/generated
|
||||
dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/src/main/resources/META-INF/native-image
|
||||
dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/src/main/generated
|
||||
.git.exec.error
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ header:
|
|||
The ASF licenses this file to You 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.
|
||||
|
|
@ -67,6 +67,7 @@ header:
|
|||
- 'CNAME'
|
||||
- 'Jenkinsfile'
|
||||
- '**/vendor/**'
|
||||
- '**/src/test/resources/certs/**'
|
||||
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocal.java'
|
||||
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocalMap.java'
|
||||
- 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java'
|
||||
|
|
@ -79,6 +80,24 @@ header:
|
|||
- 'dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/utils/TestSocketUtils.java'
|
||||
- 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java'
|
||||
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/serial/SerializingExecutor.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractAotMojo.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractDependencyFilterMojo.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/CommandLineBuilder.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/DependencyFilter.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/Exclude.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/ExcludeFilter.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/FilterableDependency.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/Include.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/IncludeFilter.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaCompilerPluginConfiguration.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaExecutable.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaProcessExecutor.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/MatchingGroupIdFilter.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/RunArguments.java'
|
||||
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/RunProcess.java'
|
||||
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/BasicJsonWriter.java'
|
||||
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/ExecutableMode.java'
|
||||
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/MemberCategory.java'
|
||||
|
||||
comment: on-failure
|
||||
|
||||
|
|
@ -171,6 +190,16 @@ dependency:
|
|||
license: Apache-2.0
|
||||
- name: org.slf4j:jul-to-slf4j
|
||||
license: Apache-2.0
|
||||
- name: org.codehaus.plexus:plexus-interpolation
|
||||
license: Apache-2.0
|
||||
- name: org.sonatype.plexus:plexus-sec-dispatcher
|
||||
license: Apache-2.0
|
||||
- name: org.sonatype.plexus:plexus-cipher
|
||||
license: Apache-2.0
|
||||
- name: com.google.protobuf:protobuf-java
|
||||
license: BSD 3-clause
|
||||
- name: com.google.protobuf:protobuf-java-util
|
||||
license: BSD 3-clause
|
||||
# multi license
|
||||
- name: org.javassist:javassist
|
||||
license: Apache-2.0
|
||||
|
|
@ -196,5 +225,9 @@ dependency:
|
|||
license: EPL-2.0
|
||||
- name: org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec
|
||||
license: CDDL-1.1
|
||||
- name: org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec
|
||||
license: EPL-2.0
|
||||
- name: org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec
|
||||
license: EPL-2.0
|
||||
excludes:
|
||||
- name: javax.xml.bind:jsr173_api
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -1,7 +1,7 @@
|
|||
# Apache Dubbo Project
|
||||
|
||||
[](https://github.com/apache/dubbo/actions/workflows/build-and-test-3.yml?query=branch%3A3.0)
|
||||
[](https://codecov.io/gh/apache/dubbo)
|
||||
[](https://github.com/apache/dubbo/actions/workflows/build-and-test-pr.yml)
|
||||
[](https://codecov.io/gh/apache/dubbo)
|
||||

|
||||

|
||||
[](http://isitmaintained.com/project/apache/dubbo "Average time to resolve an issue")
|
||||
|
|
@ -29,14 +29,14 @@ We are now collecting Dubbo user info to help us to improve Dubbo further. Kindl
|
|||
|
||||
## Getting started
|
||||
|
||||
The following code snippet comes from [Dubbo Samples](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api). You may clone the sample project and step into the `dubbo-samples-api` subdirectory before proceeding.
|
||||
The following code snippet comes from [Dubbo Samples](https://github.com/apache/dubbo-samples.git). You may clone the sample project and step into the `dubbo-samples-api` subdirectory before proceeding.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/apache/dubbo-samples.git
|
||||
cd dubbo-samples/dubbo-samples-api
|
||||
```
|
||||
|
||||
There's a [README](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions:
|
||||
There's a [README](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions:
|
||||
|
||||
### Maven dependency
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ public interface GreetingsService {
|
|||
}
|
||||
```
|
||||
|
||||
*See [api/GreetingsService.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/api/GreetingsService.java) on GitHub.*
|
||||
*See [api/GreetingsService.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/api/GreetingsService.java) on GitHub.*
|
||||
|
||||
### Implement service interface for the provider
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ public class GreetingsServiceImpl implements GreetingsService {
|
|||
}
|
||||
```
|
||||
|
||||
*See [provider/GreetingsServiceImpl.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/GreetingsServiceImpl.java) on GitHub.*
|
||||
*See [provider/GreetingsServiceImpl.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/GreetingsServiceImpl.java) on GitHub.*
|
||||
|
||||
### Start service provider
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ public class Application {
|
|||
}
|
||||
```
|
||||
|
||||
*See [provider/Application.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/Application.java) on GitHub.*
|
||||
*See [provider/Application.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-spring-xml/src/main/java/org/apache/dubbo/samples/provider/Application.java) on GitHub.*
|
||||
|
||||
### Build and run the provider
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ public class Application {
|
|||
}
|
||||
}
|
||||
```
|
||||
*See [consumer/Application.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/client/Application.java) on GitHub.*
|
||||
*See [client/Application.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/client/Application.java) on GitHub.*
|
||||
|
||||
### Build and run the consumer
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
<suppressions>
|
||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]com[\\/]alibaba[\\/]com[\\/]caucho[\\/]hessian" checks=".*"/>
|
||||
<suppress files="[\\/]build[\\/]generated[\\/]source[\\/]proto" checks=".*"/>
|
||||
<suppress files="[\\/]target[\\/]generated-sources[\\/]protobuf" checks=".*"/>
|
||||
<suppress files="Yylex\.java" checks="AvoidEscapedUnicodeCharacters"/>
|
||||
</suppressions>
|
||||
</suppressions>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@
|
|||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-rpc-injvm</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
|
|
@ -68,5 +74,11 @@
|
|||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-metrics-registry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.dubbo.rpc.cluster;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Adaptive;
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ public interface ConfiguratorFactory {
|
|||
* @param url - configurator url.
|
||||
* @return configurator instance.
|
||||
*/
|
||||
@Adaptive("protocol")
|
||||
@Adaptive(CommonConstants.PROTOCOL_KEY)
|
||||
Configurator getConfigurator(URL url);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ public interface Constants {
|
|||
*/
|
||||
boolean DEFAULT_CLUSTER_STICKY = false;
|
||||
|
||||
String ADDRESS_KEY = "address";
|
||||
|
||||
/**
|
||||
* When this attribute appears in invocation's attachment, mock invoker will be used
|
||||
*/
|
||||
|
|
@ -137,4 +135,8 @@ public interface Constants {
|
|||
String SHORTEST_RESPONSE_SLIDE_PERIOD = "shortestResponseSlidePeriod";
|
||||
|
||||
String SHOULD_FAIL_FAST_KEY = "dubbo.router.should-fail-fast";
|
||||
|
||||
String RULE_VERSION_V27 = "v2.7";
|
||||
|
||||
String RULE_VERSION_V30 = "v3.0";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.dubbo.rpc.cluster;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Adaptive;
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
|
|
@ -42,6 +43,6 @@ public interface RouterFactory {
|
|||
* @param url url
|
||||
* @return router instance
|
||||
*/
|
||||
@Adaptive("protocol")
|
||||
@Adaptive(CommonConstants.PROTOCOL_KEY)
|
||||
Router getRouter(URL url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@
|
|||
package org.apache.dubbo.rpc.cluster.configurator;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.remoting.Constants;
|
||||
import org.apache.dubbo.rpc.cluster.Configurator;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
|
@ -41,12 +45,14 @@ import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CON
|
|||
import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.CONFIG_VERSION_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
|
||||
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
|
||||
|
||||
/**
|
||||
* AbstractConfigurator
|
||||
*/
|
||||
public abstract class AbstractConfigurator implements Configurator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigurator.class);
|
||||
private static final String TILDE = "~";
|
||||
|
||||
private final URL configuratorUrl;
|
||||
|
|
@ -67,19 +73,17 @@ public abstract class AbstractConfigurator implements Configurator {
|
|||
public URL configure(URL url) {
|
||||
// If override url is not enabled or is invalid, just return.
|
||||
if (!configuratorUrl.getParameter(ENABLED_KEY, true) || configuratorUrl.getHost() == null || url == null || url.getHost() == null) {
|
||||
logger.info("Cannot apply configurator rule, the rule is disabled or is invalid: \n" + configuratorUrl);
|
||||
return url;
|
||||
}
|
||||
/*
|
||||
* This if branch is created since 2.7.0.
|
||||
*/
|
||||
|
||||
String apiVersion = configuratorUrl.getParameter(CONFIG_VERSION_KEY);
|
||||
if (StringUtils.isNotEmpty(apiVersion)) {
|
||||
if (StringUtils.isNotEmpty(apiVersion)) { // v2.7 or above
|
||||
String currentSide = url.getSide();
|
||||
String configuratorSide = configuratorUrl.getSide();
|
||||
if (currentSide.equals(configuratorSide) && CONSUMER.equals(configuratorSide) && 0 == configuratorUrl.getPort()) {
|
||||
if (currentSide.equals(configuratorSide) && CONSUMER.equals(configuratorSide)) {
|
||||
url = configureIfMatch(NetUtils.getLocalHost(), url);
|
||||
} else if (currentSide.equals(configuratorSide) && PROVIDER.equals(configuratorSide) &&
|
||||
url.getPort() == configuratorUrl.getPort()) {
|
||||
} else if (currentSide.equals(configuratorSide) && PROVIDER.equals(configuratorSide)) {
|
||||
url = configureIfMatch(url.getHost(), url);
|
||||
}
|
||||
}
|
||||
|
|
@ -118,51 +122,114 @@ public abstract class AbstractConfigurator implements Configurator {
|
|||
|
||||
private URL configureIfMatch(String host, URL url) {
|
||||
if (ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {
|
||||
// TODO, to support wildcards
|
||||
String providers = configuratorUrl.getParameter(OVERRIDE_PROVIDERS_KEY);
|
||||
if (StringUtils.isEmpty(providers) || providers.contains(url.getAddress()) || providers.contains(ANYHOST_VALUE)) {
|
||||
String configApplication = configuratorUrl.getApplication(configuratorUrl.getUsername());
|
||||
String currentApplication = url.getApplication(url.getUsername());
|
||||
if (configApplication == null || ANY_VALUE.equals(configApplication)
|
||||
|| configApplication.equals(currentApplication)) {
|
||||
Set<String> conditionKeys = new HashSet<String>();
|
||||
conditionKeys.add(CATEGORY_KEY);
|
||||
conditionKeys.add(Constants.CHECK_KEY);
|
||||
conditionKeys.add(DYNAMIC_KEY);
|
||||
conditionKeys.add(ENABLED_KEY);
|
||||
conditionKeys.add(GROUP_KEY);
|
||||
conditionKeys.add(VERSION_KEY);
|
||||
conditionKeys.add(APPLICATION_KEY);
|
||||
conditionKeys.add(SIDE_KEY);
|
||||
conditionKeys.add(CONFIG_VERSION_KEY);
|
||||
conditionKeys.add(COMPATIBLE_CONFIG_KEY);
|
||||
conditionKeys.add(INTERFACES);
|
||||
for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
boolean startWithTilde = startWithTilde(key);
|
||||
if (startWithTilde || APPLICATION_KEY.equals(key) || SIDE_KEY.equals(key)) {
|
||||
if (startWithTilde) {
|
||||
conditionKeys.add(key);
|
||||
}
|
||||
if (value != null && !ANY_VALUE.equals(value)
|
||||
&& !value.equals(url.getParameter(startWithTilde ? key.substring(1) : key))) {
|
||||
return url;
|
||||
}
|
||||
if (isV27ConditionMatchOrUnset(url)) {
|
||||
Set<String> conditionKeys = genConditionKeys();
|
||||
String apiVersion = configuratorUrl.getParameter(CONFIG_VERSION_KEY);
|
||||
if (apiVersion != null && apiVersion.startsWith(RULE_VERSION_V30)) {
|
||||
ConditionMatch matcher = (ConditionMatch) configuratorUrl.getAttribute(MATCH_CONDITION);
|
||||
if (matcher != null) {
|
||||
if (matcher.isMatch(url)) {
|
||||
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
|
||||
} else {
|
||||
logger.debug("Cannot apply configurator rule, param mismatch, current params are " + url + ", params in rule is " + matcher);
|
||||
}
|
||||
} else {
|
||||
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
|
||||
}
|
||||
} else if (isDeprecatedConditionMatch(conditionKeys, url)) {
|
||||
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot apply configurator rule, host mismatch, current host is " + host + ", host in rule is " + configuratorUrl.getHost());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private boolean startWithTilde(String key) {
|
||||
if (StringUtils.isNotEmpty(key) && key.startsWith(TILDE)) {
|
||||
return true;
|
||||
/**
|
||||
* Check if v2.7 configurator rule is set and can be matched.
|
||||
*
|
||||
* @param url the configurator rule url
|
||||
* @return true if v2.7 configurator rule is not set or the rule can be matched.
|
||||
*/
|
||||
private boolean isV27ConditionMatchOrUnset(URL url) {
|
||||
String providers = configuratorUrl.getParameter(OVERRIDE_PROVIDERS_KEY);
|
||||
if (StringUtils.isNotEmpty(providers)) {
|
||||
boolean match = false;
|
||||
String[] providerAddresses = providers.split(CommonConstants.COMMA_SEPARATOR);
|
||||
for (String address : providerAddresses) {
|
||||
if (address.equals(url.getAddress())
|
||||
|| address.equals(ANYHOST_VALUE)
|
||||
|| address.equals(ANYHOST_VALUE + CommonConstants.GROUP_CHAR_SEPARATOR + ANY_VALUE)
|
||||
|| address.equals(ANYHOST_VALUE + CommonConstants.GROUP_CHAR_SEPARATOR + url.getPort())
|
||||
|| address.equals(url.getHost())) {
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
logger.debug("Cannot apply configurator rule, provider address mismatch, current address " + url.getAddress() + ", address in rule is " + providers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
String configApplication = configuratorUrl.getApplication(configuratorUrl.getUsername());
|
||||
String currentApplication = url.getApplication(url.getUsername());
|
||||
if (configApplication != null
|
||||
&& !ANY_VALUE.equals(configApplication)
|
||||
&& !configApplication.equals(currentApplication)) {
|
||||
logger.debug("Cannot apply configurator rule, application name mismatch, current application is " + currentApplication + ", application in rule is " + configApplication);
|
||||
return false;
|
||||
}
|
||||
|
||||
String configServiceKey = configuratorUrl.getServiceKey();
|
||||
String currentServiceKey = url.getServiceKey();
|
||||
if (!ANY_VALUE.equals(configServiceKey)
|
||||
&& !configServiceKey.equals(currentServiceKey)) {
|
||||
logger.debug("Cannot apply configurator rule, service mismatch, current service is " + currentServiceKey + ", service in rule is " + configServiceKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isDeprecatedConditionMatch(Set<String> conditionKeys, URL url) {
|
||||
boolean result = true;
|
||||
for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
boolean startWithTilde = startWithTilde(key);
|
||||
if (startWithTilde || APPLICATION_KEY.equals(key) || SIDE_KEY.equals(key)) {
|
||||
if (startWithTilde) {
|
||||
conditionKeys.add(key);
|
||||
}
|
||||
if (value != null && !ANY_VALUE.equals(value)
|
||||
&& !value.equals(url.getParameter(startWithTilde ? key.substring(1) : key))) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<String> genConditionKeys() {
|
||||
Set<String> conditionKeys = new HashSet<String>();
|
||||
conditionKeys.add(CATEGORY_KEY);
|
||||
conditionKeys.add(Constants.CHECK_KEY);
|
||||
conditionKeys.add(DYNAMIC_KEY);
|
||||
conditionKeys.add(ENABLED_KEY);
|
||||
conditionKeys.add(GROUP_KEY);
|
||||
conditionKeys.add(VERSION_KEY);
|
||||
conditionKeys.add(APPLICATION_KEY);
|
||||
conditionKeys.add(SIDE_KEY);
|
||||
conditionKeys.add(CONFIG_VERSION_KEY);
|
||||
conditionKeys.add(COMPATIBLE_CONFIG_KEY);
|
||||
conditionKeys.add(INTERFACES);
|
||||
return conditionKeys;
|
||||
}
|
||||
|
||||
private boolean startWithTilde(String key) {
|
||||
return StringUtils.isNotEmpty(key) && key.startsWith(TILDE);
|
||||
}
|
||||
|
||||
protected abstract URL doConfigure(URL currentUrl, URL configUrl);
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@
|
|||
package org.apache.dubbo.rpc.cluster.configurator.override;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.AbstractConfigurator;
|
||||
|
||||
/**
|
||||
* OverrideConfigurator
|
||||
*/
|
||||
public class OverrideConfigurator extends AbstractConfigurator {
|
||||
public static final Logger logger = LoggerFactory.getLogger(OverrideConfigurator.class);
|
||||
|
||||
public OverrideConfigurator(URL url) {
|
||||
super(url);
|
||||
|
|
@ -30,6 +33,7 @@ public class OverrideConfigurator extends AbstractConfigurator {
|
|||
|
||||
@Override
|
||||
public URL doConfigure(URL currentUrl, URL configUrl) {
|
||||
logger.info("Start overriding url " + currentUrl + " with override url " + configUrl);
|
||||
return currentUrl.addParameters(configUrl.getParameters());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,11 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
|
||||
import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
|
||||
import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_CONFIGURATORS_CATEGORY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
|
||||
|
||||
/**
|
||||
* Config parser
|
||||
|
|
@ -59,6 +61,7 @@ public class ConfigParser {
|
|||
// service scope by default.
|
||||
items.forEach(item -> urls.addAll(serviceItemToUrls(item, configuratorConfig)));
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +103,10 @@ public class ConfigParser {
|
|||
if (CollectionUtils.isNotEmpty(apps)) {
|
||||
apps.forEach(app -> {
|
||||
StringBuilder tmpUrlBuilder = new StringBuilder(urlBuilder);
|
||||
urls.add(URL.valueOf(tmpUrlBuilder.append("&application=").append(app).toString()));
|
||||
urls.add(appendMatchCondition(URL.valueOf(tmpUrlBuilder.append("&application=").append(app).toString()), item));
|
||||
});
|
||||
} else {
|
||||
urls.add(URL.valueOf(urlBuilder.toString()));
|
||||
urls.add(appendMatchCondition(URL.valueOf(urlBuilder.toString()), item));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -135,7 +138,7 @@ public class ConfigParser {
|
|||
tmpUrlBuilder.append("&category=").append(APP_DYNAMIC_CONFIGURATORS_CATEGORY);
|
||||
tmpUrlBuilder.append("&configVersion=").append(config.getConfigVersion());
|
||||
|
||||
urls.add(URL.valueOf(tmpUrlBuilder.toString()));
|
||||
urls.add(appendMatchCondition(URL.valueOf(tmpUrlBuilder.toString()), item));
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
|
|
@ -167,6 +170,11 @@ public class ConfigParser {
|
|||
sb.append(OVERRIDE_PROVIDERS_KEY);
|
||||
sb.append('=');
|
||||
sb.append(CollectionUtils.join(item.getProviderAddresses(), ","));
|
||||
} else if (PROVIDER.equals(item.getSide())) {
|
||||
sb.append('&');
|
||||
sb.append(OVERRIDE_PROVIDERS_KEY);
|
||||
sb.append('=');
|
||||
sb.append(CollectionUtils.join(parseAddresses(item), ","));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
|
@ -218,4 +226,11 @@ public class ConfigParser {
|
|||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
private static URL appendMatchCondition(URL url, ConfigItem item) {
|
||||
if (item.getMatch() != null) {
|
||||
url = url.putAttribute(MATCH_CONDITION, item.getMatch());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.configurator.parser.model;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.AddressMatch;
|
||||
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.ListStringMatch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
|
||||
|
||||
public class ConditionMatch {
|
||||
private AddressMatch address;
|
||||
private ListStringMatch service;
|
||||
private ListStringMatch app;
|
||||
private List<ParamMatch> param;
|
||||
|
||||
public AddressMatch getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(AddressMatch address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public ListStringMatch getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(ListStringMatch service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public ListStringMatch getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(ListStringMatch app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public List<ParamMatch> getParam() {
|
||||
return param;
|
||||
}
|
||||
|
||||
public void setParam(List<ParamMatch> param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
public boolean isMatch(URL url) {
|
||||
if (getAddress() != null && !getAddress().isMatch(url.getAddress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getService() != null && !getService().isMatch(url.getServiceKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getApp() != null && !getApp().isMatch(url.getParameter(APPLICATION_KEY))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getParam() != null) {
|
||||
for (ParamMatch match : param) {
|
||||
if (!match.isMatch(url)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConditionMatch{" +
|
||||
"address='" + address + '\'' +
|
||||
", service='" + service + '\'' +
|
||||
", app='" + app + '\'' +
|
||||
", param='" + param + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -16,14 +16,22 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.configurator.parser.model;
|
||||
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.PojoUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RECEIVE_RULE;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ConfigItem {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ConfigItem.class);
|
||||
|
||||
public static final String GENERAL_TYPE = "general";
|
||||
public static final String WEIGHT_TYPE = "weight";
|
||||
public static final String BALANCING_TYPE = "balancing";
|
||||
|
|
@ -35,6 +43,7 @@ public class ConfigItem {
|
|||
public static final String SERVICES_KEY = "services";
|
||||
public static final String APPLICATIONS_KEY = "applications";
|
||||
public static final String PARAMETERS_KEY = "parameters";
|
||||
public static final String MATCH_KEY = "match";
|
||||
public static final String SIDE_KEY = "side";
|
||||
|
||||
private String type;
|
||||
|
|
@ -44,6 +53,7 @@ public class ConfigItem {
|
|||
private List<String> services;
|
||||
private List<String> applications;
|
||||
private Map<String, String> parameters;
|
||||
private ConditionMatch match;
|
||||
private String side;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -77,14 +87,24 @@ public class ConfigItem {
|
|||
Object applications = map.get(APPLICATIONS_KEY);
|
||||
if (applications != null && List.class.isAssignableFrom(applications.getClass())) {
|
||||
configItem.setApplications(((List<Object>) applications).stream()
|
||||
.map(String::valueOf).collect(Collectors.toList()));
|
||||
.map(String::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
Object parameters = map.get(PARAMETERS_KEY);
|
||||
if (parameters != null && Map.class.isAssignableFrom(parameters.getClass())) {
|
||||
configItem.setParameters(((Map<String, Object>) parameters).entrySet()
|
||||
.stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString())));
|
||||
.stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString())));
|
||||
}
|
||||
|
||||
try {
|
||||
Object match = map.get(MATCH_KEY);
|
||||
if (match != null && Map.class.isAssignableFrom(match.getClass())) {
|
||||
configItem.setMatch(PojoUtils.mapToPojo((Map<String, Object>) match, ConditionMatch.class));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.error(CLUSTER_FAILED_RECEIVE_RULE, " Failed to parse dynamic configuration rule", String.valueOf(map.get(MATCH_KEY)), "Error occurred when parsing rule component.", t);
|
||||
}
|
||||
|
||||
configItem.setSide((String) map.get(SIDE_KEY));
|
||||
return configItem;
|
||||
}
|
||||
|
|
@ -152,4 +172,12 @@ public class ConfigItem {
|
|||
public void setSide(String side) {
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
public ConditionMatch getMatch() {
|
||||
return match;
|
||||
}
|
||||
|
||||
public void setMatch(ConditionMatch match) {
|
||||
this.match = match;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
|||
*
|
||||
*/
|
||||
public class ConfiguratorConfig {
|
||||
public static final String MATCH_CONDITION = "MATCH_CONDITION";
|
||||
public static final String SCOPE_SERVICE = "service";
|
||||
public static final String SCOPE_APPLICATION = "application";
|
||||
public static final String CONFIG_VERSION_KEY = "configVersion";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.configurator.parser.model;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
|
||||
|
||||
public class ParamMatch {
|
||||
private String key;
|
||||
private StringMatch value;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public StringMatch getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(StringMatch value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isMatch(URL url) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String input = url.getParameter(key);
|
||||
return input != null && value.isMatch(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ParamMatch{" +
|
||||
"key='" + key + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,32 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.directory;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.Version;
|
||||
import org.apache.dubbo.common.config.Configuration;
|
||||
import org.apache.dubbo.common.config.ConfigurationUtils;
|
||||
import org.apache.dubbo.common.constants.LoggerCodeConstants;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
|
||||
import org.apache.dubbo.common.utils.ConcurrentHashSet;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.metrics.event.MetricsEvent;
|
||||
import org.apache.dubbo.metrics.registry.event.RegistryEvent;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.Directory;
|
||||
import org.apache.dubbo.rpc.cluster.Router;
|
||||
import org.apache.dubbo.rpc.cluster.RouterChain;
|
||||
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
@ -31,29 +57,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.Version;
|
||||
import org.apache.dubbo.common.config.Configuration;
|
||||
import org.apache.dubbo.common.config.ConfigurationUtils;
|
||||
import org.apache.dubbo.common.constants.LoggerCodeConstants;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
|
||||
import org.apache.dubbo.common.utils.ConcurrentHashSet;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
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 org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.Directory;
|
||||
import org.apache.dubbo.rpc.cluster.Router;
|
||||
import org.apache.dubbo.rpc.cluster.RouterChain;
|
||||
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_RECONNECT_TASK_PERIOD;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_RECONNECT_TASK_TRY_COUNT;
|
||||
|
|
@ -128,6 +131,10 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
*/
|
||||
private final int reconnectTaskPeriod;
|
||||
|
||||
private final GlobalMetricsEventMulticaster eventMulticaster;
|
||||
|
||||
private ApplicationModel applicationModel;
|
||||
|
||||
public AbstractDirectory(URL url) {
|
||||
this(url, null, false);
|
||||
}
|
||||
|
|
@ -153,7 +160,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
}
|
||||
|
||||
// remove some local only parameters
|
||||
ApplicationModel applicationModel = url.getOrDefaultApplicationModel();
|
||||
applicationModel = url.getOrDefaultApplicationModel();
|
||||
this.queryMap = applicationModel.getBeanFactory().getBean(ClusterUtils.class).mergeLocalParams(queryMap);
|
||||
|
||||
if (consumerUrl == null) {
|
||||
|
|
@ -179,12 +186,14 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
this.reconnectTaskTryCount = configuration.getInt(RECONNECT_TASK_TRY_COUNT, DEFAULT_RECONNECT_TASK_TRY_COUNT);
|
||||
this.reconnectTaskPeriod = configuration.getInt(RECONNECT_TASK_PERIOD, DEFAULT_RECONNECT_TASK_PERIOD);
|
||||
setRouterChain(routerChain);
|
||||
|
||||
eventMulticaster = applicationModel.getBeanFactory().getBean(GlobalMetricsEventMulticaster.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
|
||||
if (destroyed) {
|
||||
throw new RpcException("Directory of type " + this.getClass().getSimpleName() + " already destroyed for service " + getConsumerUrl().getServiceKey() + " from registry " + getUrl());
|
||||
throw new RpcException("Directory of type " + this.getClass().getSimpleName() + " already destroyed for service " + getConsumerUrl().getServiceKey() + " from registry " + getUrl());
|
||||
}
|
||||
|
||||
BitList<Invoker<T>> availableInvokers;
|
||||
|
|
@ -379,8 +388,15 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
invokersToRemove.removeAll(needToRemove);
|
||||
}
|
||||
|
||||
private void publishMetricsEvent(MetricsEvent event) {
|
||||
if (eventMulticaster != null) {
|
||||
eventMulticaster.publishEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDisabledInvoker(Invoker<T> invoker) {
|
||||
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_DISABLE));
|
||||
if (invokers.contains(invoker)) {
|
||||
disabledInvokers.add(invoker);
|
||||
removeValidInvoker(invoker);
|
||||
|
|
@ -390,6 +406,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
|
||||
@Override
|
||||
public void recoverDisabledInvoker(Invoker<T> invoker) {
|
||||
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_RECOVER_DISABLE));
|
||||
if (disabledInvokers.remove(invoker)) {
|
||||
try {
|
||||
addValidInvoker(invoker);
|
||||
|
|
@ -453,6 +470,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
this.invokers = invokers;
|
||||
refreshInvokerInternal();
|
||||
this.invokersInitialized = true;
|
||||
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_CURRENT, invokers.size()));
|
||||
}
|
||||
|
||||
protected void destroyInvokers() {
|
||||
|
|
@ -463,12 +481,14 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
|
|||
}
|
||||
|
||||
private boolean addValidInvoker(Invoker<T> invoker) {
|
||||
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_VALID));
|
||||
synchronized (this.validInvokers) {
|
||||
return this.validInvokers.add(invoker);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeValidInvoker(Invoker<T> invoker) {
|
||||
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_UN_VALID));
|
||||
synchronized (this.validInvokers) {
|
||||
return this.validInvokers.remove(invoker);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import org.apache.dubbo.rpc.model.ModuleModel;
|
|||
|
||||
public class DefaultGovernanceRuleRepositoryImpl implements GovernanceRuleRepository {
|
||||
|
||||
private ModuleModel moduleModel;
|
||||
private final ModuleModel moduleModel;
|
||||
|
||||
public DefaultGovernanceRuleRepositoryImpl(ModuleModel moduleModel) {
|
||||
this.moduleModel = moduleModel;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class AdaptiveLoadBalance extends AbstractLoadBalance {
|
|||
//default key
|
||||
private String attachmentKey = "mem,load";
|
||||
|
||||
private AdaptiveMetrics adaptiveMetrics;
|
||||
private final AdaptiveMetrics adaptiveMetrics;
|
||||
|
||||
public AdaptiveLoadBalance(ApplicationModel scopeModel){
|
||||
adaptiveMetrics = scopeModel.getBeanFactory().getBean(AdaptiveMetrics.class);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
|
|||
|
||||
// Every invoker has the same weight?
|
||||
boolean sameWeight = true;
|
||||
// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
|
||||
// the maxWeight of every invoker, the minWeight = 0 or the maxWeight of the last invoker
|
||||
int[] weights = new int[length];
|
||||
// The sum of weights
|
||||
int totalWeight = 0;
|
||||
|
|
@ -78,7 +78,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
|
|||
if (totalWeight > 0 && !sameWeight) {
|
||||
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
|
||||
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
|
||||
// Return a invoker based on the random value.
|
||||
// Return an invoker based on the random value.
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (offset < weights[i]) {
|
||||
return invokers.get(i);
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ public class RoundRobinLoadBalance extends AbstractLoadBalance {
|
|||
|
||||
private static final int RECYCLE_PERIOD = 60000;
|
||||
|
||||
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<>();
|
||||
|
||||
protected static class WeightedRoundRobin {
|
||||
private int weight;
|
||||
private AtomicLong current = new AtomicLong(0);
|
||||
private final AtomicLong current = new AtomicLong(0);
|
||||
private long lastUpdate;
|
||||
|
||||
public int getWeight() {
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
|
|||
|
||||
private int slidePeriod = 30_000;
|
||||
|
||||
private ConcurrentMap<RpcStatus, SlideWindowData> methodMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<RpcStatus, SlideWindowData> methodMap = new ConcurrentHashMap<>();
|
||||
|
||||
private AtomicBoolean onResetSlideWindow = new AtomicBoolean(false);
|
||||
private final AtomicBoolean onResetSlideWindow = new AtomicBoolean(false);
|
||||
|
||||
private volatile long lastUpdateTime = System.currentTimeMillis();
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
|
|||
|
||||
private long succeededOffset;
|
||||
private long succeededElapsedOffset;
|
||||
private RpcStatus rpcStatus;
|
||||
private final RpcStatus rpcStatus;
|
||||
|
||||
public SlideWindowData(RpcStatus rpcStatus) {
|
||||
this.rpcStatus = rpcStatus;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.router;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.CONFIG_VERSION_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.DYNAMIC_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.ENABLED_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
|
||||
|
|
@ -40,6 +41,7 @@ public abstract class AbstractRouterRule {
|
|||
private int priority;
|
||||
private boolean dynamic = false;
|
||||
|
||||
private String version;
|
||||
private String scope;
|
||||
private String key;
|
||||
|
||||
|
|
@ -78,6 +80,7 @@ public abstract class AbstractRouterRule {
|
|||
|
||||
setScope((String) map.get(SCOPE_KEY));
|
||||
setKey((String) map.get(KEY_KEY));
|
||||
setVersion((String) map.get(CONFIG_VERSION_KEY));
|
||||
}
|
||||
|
||||
public String getRawRule() {
|
||||
|
|
@ -151,4 +154,12 @@ public abstract class AbstractRouterRule {
|
|||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,58 +23,68 @@ import org.apache.dubbo.common.utils.CollectionUtils;
|
|||
import org.apache.dubbo.common.utils.Holder;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.common.utils.UrlUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.Constants;
|
||||
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouter;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY;
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY;
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.ADDRESS_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RUNTIME_KEY;
|
||||
|
||||
/**
|
||||
* ConditionRouter
|
||||
* It supports the conditional routing configured by "override://", in 2.6.x,
|
||||
* refer to https://dubbo.apache.org/en/docs/v2.7/user/examples/routing-rule/ .
|
||||
* For 2.7.x and later, please refer to {@link org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouter}
|
||||
* and {@link AppStateRouter}
|
||||
* refer to https://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/ .
|
||||
* Condition Router directs traffics matching the 'when condition' to a particular address subset determined by the 'then condition'.
|
||||
* One typical condition rule is like below, with
|
||||
* 1. the 'when condition' on the left side of '=>' contains matching rule like 'method=sayHello' and 'method=sayHi'
|
||||
* 2. the 'then condition' on the right side of '=>' contains matching rule like 'region=hangzhou' and 'address=*:20881'
|
||||
* <p>
|
||||
* By default, condition router support matching rules like 'foo=bar', 'foo=bar*', 'arguments[0]=bar', 'attachments[foo]=bar', 'attachments[foo]=1~100', etc.
|
||||
* It's also very easy to add customized matching rules by extending {@link ConditionMatcherFactory}
|
||||
* and {@link ValuePattern}
|
||||
* <p>
|
||||
* ---
|
||||
* scope: service
|
||||
* force: true
|
||||
* runtime: true
|
||||
* enabled: true
|
||||
* key: org.apache.dubbo.samples.governance.api.DemoService
|
||||
* conditions:
|
||||
* - method=sayHello => region=hangzhou
|
||||
* - method=sayHi => address=*:20881
|
||||
* ...
|
||||
*/
|
||||
public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
||||
public static final String NAME = "condition";
|
||||
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractStateRouter.class);
|
||||
protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
|
||||
protected static Pattern ARGUMENTS_PATTERN = Pattern.compile("arguments\\[([0-9]+)\\]");
|
||||
protected Map<String, MatchPair> whenCondition;
|
||||
protected Map<String, MatchPair> thenCondition;
|
||||
protected Map<String, ConditionMatcher> whenCondition;
|
||||
protected Map<String, ConditionMatcher> thenCondition;
|
||||
protected List<ConditionMatcherFactory> matcherFactories;
|
||||
|
||||
private boolean enabled;
|
||||
private final boolean enabled;
|
||||
|
||||
public ConditionStateRouter(URL url, String rule, boolean force, boolean enabled) {
|
||||
super(url);
|
||||
this.setForce(force);
|
||||
this.enabled = enabled;
|
||||
matcherFactories = moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getActivateExtensions();
|
||||
if (enabled) {
|
||||
this.init(rule);
|
||||
}
|
||||
|
|
@ -84,6 +94,7 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
|||
super(url);
|
||||
this.setUrl(url);
|
||||
this.setForce(url.getParameter(FORCE_KEY, false));
|
||||
matcherFactories = moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getActivateExtensions();
|
||||
this.enabled = url.getParameter(ENABLED_KEY, true);
|
||||
if (enabled) {
|
||||
init(url.getParameterAndDecoded(RULE_KEY));
|
||||
|
|
@ -99,8 +110,8 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
|||
int i = rule.indexOf("=>");
|
||||
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
|
||||
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
|
||||
Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
|
||||
Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
|
||||
Map<String, ConditionMatcher> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<>() : parseRule(whenRule);
|
||||
Map<String, ConditionMatcher> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
|
||||
// NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
|
||||
this.whenCondition = when;
|
||||
this.thenCondition = then;
|
||||
|
|
@ -109,14 +120,14 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
|||
}
|
||||
}
|
||||
|
||||
private static Map<String, MatchPair> parseRule(String rule)
|
||||
throws ParseException {
|
||||
Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
|
||||
private Map<String, ConditionMatcher> parseRule(String rule)
|
||||
throws ParseException {
|
||||
Map<String, ConditionMatcher> condition = new HashMap<>();
|
||||
if (StringUtils.isBlank(rule)) {
|
||||
return condition;
|
||||
}
|
||||
// Key-Value pair, stores both match and mismatch conditions
|
||||
MatchPair pair = null;
|
||||
ConditionMatcher matcherPair = null;
|
||||
// Multiple values
|
||||
Set<String> values = null;
|
||||
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
|
||||
|
|
@ -125,40 +136,40 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
|||
String content = matcher.group(2);
|
||||
// Start part of the condition expression.
|
||||
if (StringUtils.isEmpty(separator)) {
|
||||
pair = new MatchPair();
|
||||
condition.put(content, pair);
|
||||
matcherPair = this.getMatcher(content);
|
||||
condition.put(content, matcherPair);
|
||||
}
|
||||
// The KV part of the condition expression
|
||||
else if ("&".equals(separator)) {
|
||||
if (condition.get(content) == null) {
|
||||
pair = new MatchPair();
|
||||
condition.put(content, pair);
|
||||
matcherPair = this.getMatcher(content);
|
||||
condition.put(content, matcherPair);
|
||||
} else {
|
||||
pair = condition.get(content);
|
||||
matcherPair = condition.get(content);
|
||||
}
|
||||
}
|
||||
// The Value in the KV part.
|
||||
else if ("=".equals(separator)) {
|
||||
if (pair == null) {
|
||||
if (matcherPair == null) {
|
||||
throw new ParseException("Illegal route rule \""
|
||||
+ rule + "\", The error char '" + separator
|
||||
+ "' at index " + matcher.start() + " before \""
|
||||
+ content + "\".", matcher.start());
|
||||
+ rule + "\", The error char '" + separator
|
||||
+ "' at index " + matcher.start() + " before \""
|
||||
+ content + "\".", matcher.start());
|
||||
}
|
||||
|
||||
values = pair.matches;
|
||||
values = matcherPair.getMatches();
|
||||
values.add(content);
|
||||
}
|
||||
// The Value in the KV part.
|
||||
else if ("!=".equals(separator)) {
|
||||
if (pair == null) {
|
||||
if (matcherPair == null) {
|
||||
throw new ParseException("Illegal route rule \""
|
||||
+ rule + "\", The error char '" + separator
|
||||
+ "' at index " + matcher.start() + " before \""
|
||||
+ content + "\".", matcher.start());
|
||||
+ rule + "\", The error char '" + separator
|
||||
+ "' at index " + matcher.start() + " before \""
|
||||
+ content + "\".", matcher.start());
|
||||
}
|
||||
|
||||
values = pair.mismatches;
|
||||
values = matcherPair.getMismatches();
|
||||
values.add(content);
|
||||
}
|
||||
// The Value in the KV part, if Value have more than one items.
|
||||
|
|
@ -240,135 +251,39 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
|
|||
return this.getUrl().getParameter(RUNTIME_KEY, false);
|
||||
}
|
||||
|
||||
private ConditionMatcher getMatcher(String key) {
|
||||
for (ConditionMatcherFactory factory : matcherFactories) {
|
||||
if (factory.shouldMatch(key)) {
|
||||
return factory.createMatcher(key, moduleModel);
|
||||
}
|
||||
}
|
||||
return moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getExtension("param").createMatcher(key, moduleModel);
|
||||
}
|
||||
|
||||
boolean matchWhen(URL url, Invocation invocation) {
|
||||
return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
|
||||
if (CollectionUtils.isEmptyMap(whenCondition)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return doMatch(url, null, invocation, whenCondition, true);
|
||||
}
|
||||
|
||||
private boolean matchThen(URL url, URL param) {
|
||||
return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
|
||||
}
|
||||
|
||||
private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
|
||||
Map<String, String> sample = url.toMap();
|
||||
boolean result = false;
|
||||
for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
|
||||
String key = matchPair.getKey();
|
||||
|
||||
if (key.startsWith(Constants.ARGUMENTS)) {
|
||||
if (!matchArguments(matchPair, invocation)) {
|
||||
return false;
|
||||
} else {
|
||||
result = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String sampleValue;
|
||||
//get real invoked method name from invocation
|
||||
if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {
|
||||
sampleValue = invocation.getMethodName();
|
||||
} else if (ADDRESS_KEY.equals(key)) {
|
||||
sampleValue = url.getAddress();
|
||||
} else if (HOST_KEY.equals(key)) {
|
||||
sampleValue = url.getHost();
|
||||
} else {
|
||||
sampleValue = sample.get(key);
|
||||
}
|
||||
if (sampleValue != null) {
|
||||
if (!matchPair.getValue().isMatch(sampleValue, param)) {
|
||||
return false;
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
//not pass the condition
|
||||
if (!matchPair.getValue().matches.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* analysis the arguments in the rule.
|
||||
* Examples would be like this:
|
||||
* "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
|
||||
* "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
|
||||
* @param matchPair
|
||||
* @param invocation
|
||||
* @return
|
||||
*/
|
||||
public boolean matchArguments(Map.Entry<String, MatchPair> matchPair, Invocation invocation) {
|
||||
try {
|
||||
// split the rule
|
||||
String key = matchPair.getKey();
|
||||
String[] expressArray = key.split("\\.");
|
||||
String argumentExpress = expressArray[0];
|
||||
final Matcher matcher = ARGUMENTS_PATTERN.matcher(argumentExpress);
|
||||
if (!matcher.find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//extract the argument index
|
||||
int index = Integer.parseInt(matcher.group(1));
|
||||
if (index < 0 || index > invocation.getArguments().length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//extract the argument value
|
||||
Object object = invocation.getArguments()[index];
|
||||
|
||||
if (matchPair.getValue().isMatch(String.valueOf(object), null)) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,"condition state router arguments match failed","","Arguments match failed, matchPair[]" + matchPair + "] invocation[" + invocation + "]",e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static final class MatchPair {
|
||||
final Set<String> matches = new HashSet<String>();
|
||||
final Set<String> mismatches = new HashSet<String>();
|
||||
|
||||
private boolean isMatch(String value, URL param) {
|
||||
if (!matches.isEmpty() && mismatches.isEmpty()) {
|
||||
for (String match : matches) {
|
||||
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mismatches.isEmpty() && matches.isEmpty()) {
|
||||
for (String mismatch : mismatches) {
|
||||
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!matches.isEmpty() && !mismatches.isEmpty()) {
|
||||
//when both mismatches and matches contain the same value, then using mismatches first
|
||||
for (String mismatch : mismatches) {
|
||||
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (String match : matches) {
|
||||
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (CollectionUtils.isEmptyMap(thenCondition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return doMatch(url, param, null, thenCondition, false);
|
||||
}
|
||||
|
||||
private boolean doMatch(URL url, URL param, Invocation invocation, Map<String, ConditionMatcher> conditions, boolean isWhenCondition) {
|
||||
Map<String, String> sample = url.toOriginalMap();
|
||||
for (Map.Entry<String, ConditionMatcher> entry : conditions.entrySet()) {
|
||||
ConditionMatcher matchPair = entry.getValue();
|
||||
if (!matchPair.isMatch(sample, param, invocation, isWhenCondition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,10 @@ import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory;
|
|||
public class AppStateRouterFactory implements StateRouterFactory {
|
||||
public static final String NAME = "app";
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private volatile StateRouter router;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> StateRouter<T> getRouter(Class<T> interfaceClass, URL url) {
|
||||
if (router != null) {
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAIL
|
|||
*/
|
||||
public abstract class ListenableStateRouter<T> extends AbstractStateRouter<T> implements ConfigurationListener {
|
||||
public static final String NAME = "LISTENABLE_ROUTER";
|
||||
private static final String RULE_SUFFIX = ".condition-router";
|
||||
public static final String RULE_SUFFIX = ".condition-router";
|
||||
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
|
||||
private volatile ConditionRouterRule routerRule;
|
||||
private volatile List<ConditionStateRouter<T>> conditionRouters = Collections.emptyList();
|
||||
private String ruleKey;
|
||||
private final String ruleKey;
|
||||
|
||||
public ListenableStateRouter(URL url, String ruleKey) {
|
||||
super(url);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
|
||||
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
|
||||
import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
|
||||
|
||||
/**
|
||||
* Application level router, "application.condition-router"
|
||||
*/
|
||||
public class ProviderAppStateRouter<T> extends ListenableStateRouter<T> {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
|
||||
public static final String NAME = "PROVIDER_APP_ROUTER";
|
||||
private String application;
|
||||
private final String currentApplication;
|
||||
|
||||
public ProviderAppStateRouter(URL url) {
|
||||
super(url, url.getApplication());
|
||||
this.currentApplication = url.getApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(BitList<Invoker<T>> invokers) {
|
||||
if (CollectionUtils.isEmpty(invokers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Invoker<T> invoker = invokers.get(0);
|
||||
URL url = invoker.getUrl();
|
||||
String providerApplication = url.getRemoteApplication();
|
||||
|
||||
// provider application is empty or equals with the current application
|
||||
if (isEmpty(providerApplication) || providerApplication.equals(currentApplication)) {
|
||||
logger.warn(CLUSTER_TAG_ROUTE_EMPTY, "condition router get providerApplication is empty, will not subscribe to provider app rules.", "", "");
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (!providerApplication.equals(application)) {
|
||||
if (StringUtils.isNotEmpty(application)) {
|
||||
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
|
||||
}
|
||||
String key = providerApplication + RULE_SUFFIX;
|
||||
this.getRuleRepository().addListener(key, this);
|
||||
application = providerApplication;
|
||||
String rawRule = this.getRuleRepository().getRule(key, DynamicConfiguration.DEFAULT_GROUP);
|
||||
if (StringUtils.isNotEmpty(rawRule)) {
|
||||
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.CacheableStateRouterFactory;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
|
||||
|
||||
/**
|
||||
* Tag router factory
|
||||
*/
|
||||
@Activate(order = 145)
|
||||
public class ProviderAppStateRouterFactory extends CacheableStateRouterFactory {
|
||||
|
||||
public static final String NAME = "provider-app";
|
||||
|
||||
@Override
|
||||
protected <T> StateRouter<T> createRouter(Class<T> interfaceClass, URL url) {
|
||||
return new ProviderAppStateRouter<>(url);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY;
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
|
||||
/**
|
||||
* The abstract implementation of ConditionMatcher, records the match and mismatch patterns of this matcher while at the same time
|
||||
* provides the common match logics.
|
||||
*/
|
||||
public abstract class AbstractConditionMatcher implements ConditionMatcher {
|
||||
public static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractConditionMatcher.class);
|
||||
public static final String DOES_NOT_FOUND_VALUE = "dubbo_internal_not_found_argument_condition_value";
|
||||
final Set<String> matches = new HashSet<>();
|
||||
final Set<String> mismatches = new HashSet<>();
|
||||
private final ModuleModel model;
|
||||
private final List<ValuePattern> valueMatchers;
|
||||
protected final String key;
|
||||
|
||||
public AbstractConditionMatcher(String key, ModuleModel model) {
|
||||
this.key = key;
|
||||
this.model = model;
|
||||
this.valueMatchers = model.getExtensionLoader(ValuePattern.class).getActivateExtensions();
|
||||
}
|
||||
|
||||
public static String getSampleValueFromUrl(String conditionKey, Map<String, String> sample, URL param, Invocation invocation) {
|
||||
String sampleValue;
|
||||
//get real invoked method name from invocation
|
||||
if (invocation != null && (METHOD_KEY.equals(conditionKey) || METHODS_KEY.equals(conditionKey))) {
|
||||
sampleValue = invocation.getMethodName();
|
||||
} else {
|
||||
sampleValue = sample.get(conditionKey);
|
||||
}
|
||||
|
||||
return sampleValue;
|
||||
}
|
||||
|
||||
public boolean isMatch(Map<String, String> sample, URL param, Invocation invocation, boolean isWhenCondition) {
|
||||
String value = getValue(sample, param, invocation);
|
||||
if (value == null) {
|
||||
// if key does not present in whichever of url, invocation or attachment based on the matcher type, then return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!matches.isEmpty() && mismatches.isEmpty()) {
|
||||
for (String match : matches) {
|
||||
if (doPatternMatch(match, value, param, invocation, isWhenCondition)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mismatches.isEmpty() && matches.isEmpty()) {
|
||||
for (String mismatch : mismatches) {
|
||||
if (doPatternMatch(mismatch, value, param, invocation, isWhenCondition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!matches.isEmpty() && !mismatches.isEmpty()) {
|
||||
//when both mismatches and matches contain the same value, then using mismatches first
|
||||
for (String mismatch : mismatches) {
|
||||
if (doPatternMatch(mismatch, value, param, invocation, isWhenCondition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (String match : matches) {
|
||||
if (doPatternMatch(match, value, param, invocation, isWhenCondition)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getMatches() {
|
||||
return matches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getMismatches() {
|
||||
return mismatches;
|
||||
}
|
||||
|
||||
// range, equal or other methods
|
||||
protected boolean doPatternMatch(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
|
||||
for (ValuePattern valueMatcher : valueMatchers) {
|
||||
if (valueMatcher.shouldMatch(pattern)) {
|
||||
return valueMatcher.match(pattern, value, url, invocation, isWhenCondition);
|
||||
}
|
||||
}
|
||||
// this should never happen.
|
||||
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Executing condition rule value match expression error.", "pattern is " + pattern + ", value is " + value + ", condition type " + (isWhenCondition ? "when" : "then"), "There should at least has one ValueMatcher instance that applies to all patterns, will force to use wildcard matcher now.");
|
||||
|
||||
ValuePattern paramValueMatcher = model.getExtensionLoader(ValuePattern.class).getExtension("wildcard");
|
||||
return paramValueMatcher.match(pattern, value, url, invocation, isWhenCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get value from different places of the request context, for example, url, attachment and invocation.
|
||||
* This makes condition rule possible to check values in any place of a request.
|
||||
*/
|
||||
protected abstract String getValue(Map<String, String> sample, URL url, Invocation invocation);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ConditionMatcher represents a specific match condition of a condition rule.
|
||||
* <p>
|
||||
* The following condition rule '=bar&arguments[0]=hello* => region=hangzhou' consists of three ConditionMatchers:
|
||||
* 1. UrlParamConditionMatcher represented by 'foo=bar'
|
||||
* 2. ArgumentsConditionMatcher represented by 'arguments[0]=hello*'
|
||||
* 3. UrlParamConditionMatcher represented by 'region=hangzhou'
|
||||
* <p>
|
||||
* It's easy to define your own matcher by extending {@link ConditionMatcherFactory}
|
||||
*/
|
||||
public interface ConditionMatcher {
|
||||
|
||||
/**
|
||||
* Determines if the patterns of this matcher matches with request context.
|
||||
*
|
||||
* @param sample request context in provider url
|
||||
* @param param request context in consumer url
|
||||
* @param invocation request context in invocation, typically, service, method, arguments and attachments
|
||||
* @param isWhenCondition condition type
|
||||
* @return the matching result
|
||||
*/
|
||||
boolean isMatch(Map<String, String> sample, URL param, Invocation invocation, boolean isWhenCondition);
|
||||
|
||||
/**
|
||||
* match patterns extracted from when condition
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Set<String> getMatches();
|
||||
|
||||
/**
|
||||
* mismatch patterns extracted from then condition
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Set<String> getMismatches();
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
|
||||
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
/**
|
||||
* Factory of ConditionMatcher instances.
|
||||
*/
|
||||
@SPI
|
||||
public interface ConditionMatcherFactory {
|
||||
/**
|
||||
* Check if the key is of the form of the current matcher type which this factory instance represents..
|
||||
*
|
||||
* @param key the key of a particular form
|
||||
* @return true if matches, otherwise false
|
||||
*/
|
||||
boolean shouldMatch(String key);
|
||||
|
||||
/**
|
||||
* Create a matcher instance for the key.
|
||||
*
|
||||
* @param key the key value conforms to a specific matcher specification
|
||||
* @param model module model
|
||||
* @return the specific matcher instance
|
||||
*/
|
||||
ConditionMatcher createMatcher(String key, ModuleModel model);
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.argument;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
|
||||
/**
|
||||
* analysis the arguments in the rule.
|
||||
* Examples would be like this:
|
||||
* "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
|
||||
* "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
|
||||
*/
|
||||
@Activate
|
||||
public class ArgumentConditionMatcher extends AbstractConditionMatcher {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ArgumentConditionMatcher.class);
|
||||
private static final Pattern ARGUMENTS_PATTERN = Pattern.compile("arguments\\[([0-9]+)\\]");
|
||||
|
||||
public ArgumentConditionMatcher(String key, ModuleModel model) {
|
||||
super(key, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(Map<String, String> sample, URL url, Invocation invocation) {
|
||||
try {
|
||||
// split the rule
|
||||
String[] expressArray = key.split("\\.");
|
||||
String argumentExpress = expressArray[0];
|
||||
final Matcher matcher = ARGUMENTS_PATTERN.matcher(argumentExpress);
|
||||
if (!matcher.find()) {
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
|
||||
//extract the argument index
|
||||
int index = Integer.parseInt(matcher.group(1));
|
||||
if (index < 0 || index > invocation.getArguments().length) {
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
|
||||
//extract the argument value
|
||||
return String.valueOf(invocation.getArguments()[index]);
|
||||
} catch (Exception e) {
|
||||
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Parse argument match condition failed", "", "Invalid , will ignore., ", e);
|
||||
}
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.argument;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.cluster.Constants;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
@Activate(order = 300)
|
||||
public class ArgumentConditionMatcherFactory implements ConditionMatcherFactory {
|
||||
|
||||
@Override
|
||||
public boolean shouldMatch(String key) {
|
||||
return key.startsWith(Constants.ARGUMENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionMatcher createMatcher(String key, ModuleModel model) {
|
||||
return new ArgumentConditionMatcher(key, model);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
|
||||
/**
|
||||
* analysis the arguments in the rule.
|
||||
* Examples would be like this:
|
||||
* "attachments[foo]=bar", whenCondition is that the attachment value of 'foo' is equal to 'bar'.
|
||||
*/
|
||||
@Activate
|
||||
public class AttachmentConditionMatcher extends AbstractConditionMatcher {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AttachmentConditionMatcher.class);
|
||||
private static final Pattern ATTACHMENTS_PATTERN = Pattern.compile("attachments\\[(.+)\\]");
|
||||
|
||||
public AttachmentConditionMatcher(String key, ModuleModel model) {
|
||||
super(key, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getValue(Map<String, String> sample, URL url, Invocation invocation) {
|
||||
try {
|
||||
// split the rule
|
||||
String[] expressArray = key.split("\\.");
|
||||
String argumentExpress = expressArray[0];
|
||||
final Matcher matcher = ATTACHMENTS_PATTERN.matcher(argumentExpress);
|
||||
if (!matcher.find()) {
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
|
||||
//extract the argument index
|
||||
String attachmentKey = matcher.group(1);
|
||||
if (StringUtils.isEmpty(attachmentKey)) {
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
|
||||
//extract the argument value
|
||||
return invocation.getAttachment(attachmentKey);
|
||||
} catch (Exception e) {
|
||||
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,
|
||||
"condition state router attachment match failed",
|
||||
"",
|
||||
"Invalid match condition: " + key,
|
||||
e);
|
||||
}
|
||||
return DOES_NOT_FOUND_VALUE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
@Activate(order = 200)
|
||||
public class AttachmentConditionMatcherFactory implements ConditionMatcherFactory {
|
||||
private static final String ATTACHMENTS = "attachments";
|
||||
|
||||
@Override
|
||||
public boolean shouldMatch(String key) {
|
||||
return key.startsWith(ATTACHMENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionMatcher createMatcher(String key, ModuleModel model) {
|
||||
return new AttachmentConditionMatcher(key, model);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.param;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This instance will be loaded separately to ensure it always gets executed as the last matcher.
|
||||
* So we don't put Active annotation here.
|
||||
*/
|
||||
public class UrlParamConditionMatcher extends AbstractConditionMatcher {
|
||||
|
||||
public UrlParamConditionMatcher(String key, ModuleModel model) {
|
||||
super(key, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getValue(Map<String, String> sample, URL url, Invocation invocation) {
|
||||
return getSampleValueFromUrl(key, sample, url, invocation);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.param;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
|
||||
import org.apache.dubbo.rpc.model.ModuleModel;
|
||||
|
||||
// Make sure this is the last matcher being executed.
|
||||
@Activate(order = Integer.MAX_VALUE)
|
||||
public class UrlParamConditionMatcherFactory implements ConditionMatcherFactory {
|
||||
@Override
|
||||
public boolean shouldMatch(String key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionMatcher createMatcher(String key, ModuleModel model) {
|
||||
return new UrlParamConditionMatcher(key, model);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@SPI
|
||||
public interface ValuePattern {
|
||||
/**
|
||||
* Is the input pattern of a specific form, for example, range pattern '1~100', wildcard pattern 'hello*', etc.
|
||||
*
|
||||
* @param pattern the match or mismatch pattern
|
||||
* @return true or false
|
||||
*/
|
||||
boolean shouldMatch(String pattern);
|
||||
|
||||
/**
|
||||
* Is the pattern matches with the request context
|
||||
*
|
||||
* @param pattern pattern value extracted from condition rule
|
||||
* @param value the real value extracted from request context
|
||||
* @param url request context in consumer url
|
||||
* @param invocation request context in invocation
|
||||
* @param isWhenCondition condition type
|
||||
* @return true if successfully match
|
||||
*/
|
||||
boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition);
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.range;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
|
||||
/**
|
||||
* Matches with patterns like 'key=1~100', 'key=~100' or 'key=1~'
|
||||
*/
|
||||
@Activate(order = 100)
|
||||
public class RangeValuePattern implements ValuePattern {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RangeValuePattern.class);
|
||||
|
||||
@Override
|
||||
public boolean shouldMatch(String pattern) {
|
||||
return pattern.contains("~");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
|
||||
boolean defaultValue = !isWhenCondition;
|
||||
try {
|
||||
int intValue = StringUtils.parseInteger(value);
|
||||
|
||||
String[] arr = pattern.split("~");
|
||||
if (arr.length < 2) {
|
||||
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "", "", "Invalid condition rule " + pattern + " or value " + value + ", will ignore.");
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
String rawStart = arr[0];
|
||||
String rawEnd = arr[1];
|
||||
|
||||
if (StringUtils.isEmpty(rawStart) && StringUtils.isEmpty(rawEnd)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(rawStart)) {
|
||||
int end = StringUtils.parseInteger(rawEnd);
|
||||
if (intValue > end) {
|
||||
return false;
|
||||
}
|
||||
} else if (StringUtils.isEmpty(rawEnd)) {
|
||||
int start = StringUtils.parseInteger(rawStart);
|
||||
if (intValue < start) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int start = StringUtils.parseInteger(rawStart);
|
||||
int end = StringUtils.parseInteger(rawEnd);
|
||||
if (intValue < start || intValue > end) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Parse integer error", "", "Invalid condition rule " + pattern + " or value " + value + ", will ignore.", e);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.wildcard;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.common.utils.UrlUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
|
||||
|
||||
/**
|
||||
* Matches with patterns like 'key=hello', 'key=hello*', 'key=*hello', 'key=h*o' or 'key=*'
|
||||
* <p>
|
||||
* This pattern evaluator must be the last one being executed.
|
||||
*/
|
||||
@Activate(order = Integer.MAX_VALUE)
|
||||
public class WildcardValuePattern implements ValuePattern {
|
||||
@Override
|
||||
public boolean shouldMatch(String key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
|
||||
return UrlUtils.isMatchGlobPattern(pattern, value, url);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.router.mesh.route;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
|
@ -109,7 +110,7 @@ public class MeshRuleCache<T> {
|
|||
List<Invoker<T>> subsetInvokers = subsetMap.computeIfAbsent(subsetName, (k) -> new BitList<>(invokers.getOriginList(), true));
|
||||
|
||||
Map<String, String> labels = subset.getLabels();
|
||||
if (containMapKeyValue(invoker.getUrl().getServiceParameters(protocolServiceKey), labels)) {
|
||||
if (isLabelMatch(invoker.getUrl(), protocolServiceKey, labels)) {
|
||||
subsetInvokers.add(invoker);
|
||||
matched = true;
|
||||
}
|
||||
|
|
@ -133,7 +134,7 @@ public class MeshRuleCache<T> {
|
|||
return new MeshRuleCache<>(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), BitList.emptyList());
|
||||
}
|
||||
|
||||
protected static boolean containMapKeyValue(Map<String, String> originMap, Map<String, String> inputMap) {
|
||||
protected static boolean isLabelMatch(URL url, String protocolServiceKey, Map<String, String> inputMap) {
|
||||
if (inputMap == null || inputMap.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -142,7 +143,7 @@ public class MeshRuleCache<T> {
|
|||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
String originMapValue = originMap.get(key);
|
||||
String originMapValue = url.getOriginalServiceParameter(protocolServiceKey, key);
|
||||
if (!value.equals(originMapValue)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match;
|
||||
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
|
||||
import static org.apache.dubbo.common.utils.NetUtils.matchIpExpression;
|
||||
import static org.apache.dubbo.common.utils.UrlUtils.isMatchGlobPattern;
|
||||
|
||||
public class AddressMatch {
|
||||
public static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AddressMatch.class);
|
||||
private String wildcard;
|
||||
private String cird;
|
||||
private String exact;
|
||||
|
||||
public String getWildcard() {
|
||||
return wildcard;
|
||||
}
|
||||
|
||||
public void setWildcard(String wildcard) {
|
||||
this.wildcard = wildcard;
|
||||
}
|
||||
|
||||
public String getCird() {
|
||||
return cird;
|
||||
}
|
||||
|
||||
public void setCird(String cird) {
|
||||
this.cird = cird;
|
||||
}
|
||||
|
||||
public String getExact() {
|
||||
return exact;
|
||||
}
|
||||
|
||||
public void setExact(String exact) {
|
||||
this.exact = exact;
|
||||
}
|
||||
|
||||
public boolean isMatch(String input) {
|
||||
if (getCird() != null && input != null) {
|
||||
try {
|
||||
return input.equals(getCird()) || matchIpExpression(getCird(), input);
|
||||
} catch (UnknownHostException e) {
|
||||
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Executing routing rule match expression error.", "", String.format("Error trying to match cird formatted address %s with input %s in AddressMatch.", getCird(), input), e);
|
||||
}
|
||||
}
|
||||
if (getWildcard() != null && input != null) {
|
||||
if (ANYHOST_VALUE.equals(getWildcard()) || ANY_VALUE.equals(getWildcard())) {
|
||||
return true;
|
||||
}
|
||||
// FIXME
|
||||
return isMatchGlobPattern(getWildcard(), input);
|
||||
}
|
||||
if (getExact() != null && input != null) {
|
||||
return input.equals(getExact());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,12 +18,15 @@
|
|||
package org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match;
|
||||
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
|
||||
|
||||
public class StringMatch {
|
||||
private String exact;
|
||||
private String prefix;
|
||||
private String regex;
|
||||
private String noempty;
|
||||
private String empty;
|
||||
private String wildcard;
|
||||
|
||||
|
||||
public String getExact() {
|
||||
|
|
@ -66,6 +69,14 @@ public class StringMatch {
|
|||
this.empty = empty;
|
||||
}
|
||||
|
||||
public String getWildcard() {
|
||||
return wildcard;
|
||||
}
|
||||
|
||||
public void setWildcard(String wildcard) {
|
||||
this.wildcard = wildcard;
|
||||
}
|
||||
|
||||
public boolean isMatch(String input) {
|
||||
if (getExact() != null && input != null) {
|
||||
return input.equals(getExact());
|
||||
|
|
@ -73,6 +84,9 @@ public class StringMatch {
|
|||
return input.startsWith(getPrefix());
|
||||
} else if (getRegex() != null && input != null) {
|
||||
return input.matches(getRegex());
|
||||
} else if (getWildcard() != null && input != null) {
|
||||
// only supports "*"
|
||||
return input.equals(getWildcard()) || ANY_VALUE.equals(getWildcard());
|
||||
} else if (getEmpty() != null) {
|
||||
return input == null || "".equals(input);
|
||||
} else if (getNoempty() != null) {
|
||||
|
|
@ -82,7 +96,6 @@ public class StringMatch {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StringMatch{" +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.script.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.CacheableStateRouterFactory;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
|
||||
|
||||
@Activate(order = 200)
|
||||
public class AppScriptRouterFactory extends CacheableStateRouterFactory {
|
||||
public static final String NAME = "script";
|
||||
|
||||
@Override
|
||||
protected <T> StateRouter<T> createRouter(Class<T> interfaceClass, URL url) {
|
||||
return new AppScriptStateRouter<>(url);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.script.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
|
||||
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
|
||||
import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
|
||||
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.common.utils.Holder;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
|
||||
import org.apache.dubbo.rpc.cluster.router.script.ScriptStateRouter;
|
||||
import org.apache.dubbo.rpc.cluster.router.script.config.model.ScriptRule;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID;
|
||||
import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_SCRIPT_TYPE_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RUNTIME_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.TYPE_KEY;
|
||||
|
||||
public class AppScriptStateRouter<T> extends AbstractStateRouter<T> implements ConfigurationListener {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AppScriptStateRouter.class);
|
||||
private static final String RULE_SUFFIX = ".script-router";
|
||||
|
||||
private ScriptRule scriptRule;
|
||||
private ScriptStateRouter<T> scriptRouter;
|
||||
private String application;
|
||||
|
||||
public AppScriptStateRouter(URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BitList<Invoker<T>> doRoute(BitList<Invoker<T>> invokers,
|
||||
URL url,
|
||||
Invocation invocation,
|
||||
boolean needToPrintMessage,
|
||||
Holder<RouterSnapshotNode<T>> routerSnapshotNodeHolder,
|
||||
Holder<String> messageHolder) throws RpcException {
|
||||
if (scriptRouter == null || !scriptRule.isValid() || !scriptRule.isEnabled()) {
|
||||
if (needToPrintMessage) {
|
||||
messageHolder.set("Directly return from script router. Reason: Invokers from previous router is empty or script is not enabled. Script rule is: " + (scriptRule == null ? "null" : scriptRule.getRawRule()));
|
||||
}
|
||||
return invokers;
|
||||
}
|
||||
|
||||
invokers = scriptRouter.route(invokers, url, invocation, needToPrintMessage, routerSnapshotNodeHolder);
|
||||
|
||||
if (needToPrintMessage) {
|
||||
messageHolder.set(messageHolder.get());
|
||||
}
|
||||
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void process(ConfigChangedEvent event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Notification of script rule change, type is: " + event.getChangeType() + ", raw rule is:\n " +
|
||||
event.getContent());
|
||||
}
|
||||
|
||||
try {
|
||||
if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
|
||||
this.scriptRule = null;
|
||||
} else {
|
||||
this.scriptRule = ScriptRule.parse(event.getContent());
|
||||
URL scriptUrl = getUrl()
|
||||
.addParameter(TYPE_KEY, isEmpty(scriptRule.getType()) ? DEFAULT_SCRIPT_TYPE_KEY : scriptRule.getType())
|
||||
.addParameterAndEncoded(RULE_KEY, scriptRule.getScript())
|
||||
.addParameter(FORCE_KEY, scriptRule.isForce())
|
||||
.addParameter(RUNTIME_KEY, scriptRule.isRuntime());
|
||||
scriptRouter = new ScriptStateRouter<>(scriptUrl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(CLUSTER_TAG_ROUTE_INVALID, "Failed to parse the raw tag router rule", "", "Failed to parse the raw tag router rule and it will not take effect, please check if the " +
|
||||
"rule matches with the template, the raw rule is:\n ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(BitList<Invoker<T>> invokers) {
|
||||
if (CollectionUtils.isEmpty(invokers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Invoker<T> invoker = invokers.get(0);
|
||||
URL url = invoker.getUrl();
|
||||
String providerApplication = url.getRemoteApplication();
|
||||
|
||||
if (isEmpty(providerApplication)) {
|
||||
logger.error(CLUSTER_TAG_ROUTE_EMPTY, "tag router get providerApplication is empty", "", "TagRouter must getConfig from or subscribe to a specific application, but the application " +
|
||||
"in this TagRouter is not specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (!providerApplication.equals(application)) {
|
||||
if (StringUtils.isNotEmpty(application)) {
|
||||
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
|
||||
}
|
||||
String key = providerApplication + RULE_SUFFIX;
|
||||
this.getRuleRepository().addListener(key, this);
|
||||
application = providerApplication;
|
||||
String rawRule = this.getRuleRepository().getRule(key, DynamicConfiguration.DEFAULT_GROUP);
|
||||
if (StringUtils.isNotEmpty(rawRule)) {
|
||||
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (StringUtils.isNotEmpty(application)) {
|
||||
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
|
||||
}
|
||||
}
|
||||
|
||||
// for testing purpose
|
||||
public void setScriptRule(ScriptRule scriptRule) {
|
||||
this.scriptRule = scriptRule;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.script.config.model;
|
||||
|
||||
import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
|
||||
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ScriptRule extends AbstractRouterRule {
|
||||
private static final String TYPE_KEY = "type";
|
||||
private static final String SCRIPT_KEY = "script";
|
||||
private String type;
|
||||
private String script;
|
||||
|
||||
public static ScriptRule parse(String rawRule) {
|
||||
Yaml yaml = new Yaml(new SafeConstructor());
|
||||
Map<String, Object> map = yaml.load(rawRule);
|
||||
|
||||
ScriptRule rule = new ScriptRule();
|
||||
rule.parseFromMap0(map);
|
||||
rule.setRawRule(rawRule);
|
||||
|
||||
Object rawType = map.get(TYPE_KEY);
|
||||
if (rawType != null) {
|
||||
rule.setType((String) rawType);
|
||||
}
|
||||
|
||||
Object rawScript = map.get(SCRIPT_KEY);
|
||||
if (rawScript != null) {
|
||||
rule.setScript((String) rawScript);
|
||||
} else {
|
||||
rule.setValid(false);
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public void setScript(String script) {
|
||||
this.script = script;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,8 +43,10 @@ public abstract class AbstractStateRouter<T> implements StateRouter<T> {
|
|||
*/
|
||||
private final boolean shouldFailFast;
|
||||
|
||||
protected ModuleModel moduleModel;
|
||||
|
||||
public AbstractStateRouter(URL url) {
|
||||
ModuleModel moduleModel = url.getOrDefaultModuleModel();
|
||||
moduleModel = url.getOrDefaultModuleModel();
|
||||
this.ruleRepository = moduleModel.getExtensionLoader(GovernanceRuleRepository.class).getDefaultExtension();
|
||||
this.url = url;
|
||||
this.shouldFailFast = Boolean.parseBoolean(ConfigurationUtils.getProperty(moduleModel, Constants.SHOULD_FAIL_FAST_KEY, "true"));
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||
* @param <E>
|
||||
* @since 3.0
|
||||
*/
|
||||
public class BitList<E> extends AbstractList<E> {
|
||||
public class BitList<E> extends AbstractList<E> implements Cloneable {
|
||||
private final BitSet rootSet;
|
||||
private volatile List<E> originList;
|
||||
private final static BitList emptyList = new BitList(Collections.emptyList());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.dubbo.rpc.cluster.router.state;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.Adaptive;
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
|
|
@ -29,6 +30,6 @@ public interface StateRouterFactory {
|
|||
* @return router instance
|
||||
* @since 3.0
|
||||
*/
|
||||
@Adaptive("protocol")
|
||||
@Adaptive(CommonConstants.PROTOCOL_KEY)
|
||||
<T> StateRouter<T> getRouter(Class<T> interfaceClass, URL url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
|||
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule;
|
||||
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRuleParser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
|
||||
|
|
@ -53,8 +53,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(TagStateRouter.class);
|
||||
private static final String RULE_SUFFIX = ".tag-router";
|
||||
|
||||
private TagRouterRule tagRouterRule;
|
||||
private volatile TagRouterRule tagRouterRule;
|
||||
private String application;
|
||||
private volatile BitList<Invoker<T>> invokers = BitList.emptyList();
|
||||
|
||||
public TagStateRouter(URL url) {
|
||||
super(url);
|
||||
|
|
@ -71,7 +72,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
|
||||
this.tagRouterRule = null;
|
||||
} else {
|
||||
this.tagRouterRule = TagRuleParser.parse(event.getContent());
|
||||
TagRouterRule rule = TagRuleParser.parse(event.getContent());
|
||||
rule.init(this);
|
||||
this.tagRouterRule = rule;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(CLUSTER_TAG_ROUTE_INVALID,"Failed to parse the raw tag router rule","","Failed to parse the raw tag router rule and it will not take effect, please check if the " +
|
||||
|
|
@ -103,9 +106,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
|
||||
// if we are requesting for a Provider with a specific tag
|
||||
if (StringUtils.isNotEmpty(tag)) {
|
||||
List<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
|
||||
Set<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
|
||||
// filter by dynamic tag group first
|
||||
if (CollectionUtils.isNotEmpty(addresses)) {
|
||||
if (addresses != null) { // null means tag not set
|
||||
result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
|
||||
// if result is not null OR it's null but force=true, return result directly
|
||||
if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
|
||||
|
|
@ -129,8 +132,10 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
}
|
||||
// FAILOVER: return all Providers without any tags.
|
||||
else {
|
||||
BitList<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
|
||||
tagRouterRuleCopy.getAddresses()));
|
||||
BitList<Invoker<T>> tmp = filterInvoker(
|
||||
invokers,
|
||||
invoker -> addressNotMatches(invoker.getUrl(), tagRouterRuleCopy.getAddresses())
|
||||
);
|
||||
if (needToPrintMessage) {
|
||||
messageHolder.set("FAILOVER: return all Providers without any tags");
|
||||
}
|
||||
|
|
@ -139,7 +144,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
} else {
|
||||
// List<String> addresses = tagRouterRule.filter(providerApp);
|
||||
// return all addresses in dynamic tag group.
|
||||
List<String> addresses = tagRouterRuleCopy.getAddresses();
|
||||
Set<String> addresses = tagRouterRuleCopy.getAddresses();
|
||||
if (CollectionUtils.isNotEmpty(addresses)) {
|
||||
result = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(), addresses));
|
||||
// 1. all addresses are in dynamic tag group, return empty list.
|
||||
|
|
@ -157,7 +162,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
}
|
||||
return filterInvoker(result, invoker -> {
|
||||
String localTag = invoker.getUrl().getParameter(TAG_KEY);
|
||||
return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
|
||||
return StringUtils.isEmpty(localTag);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -219,15 +224,15 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
return newInvokers;
|
||||
}
|
||||
|
||||
private boolean addressMatches(URL url, List<String> addresses) {
|
||||
private boolean addressMatches(URL url, Set<String> addresses) {
|
||||
return addresses != null && checkAddressMatch(addresses, url.getHost(), url.getPort());
|
||||
}
|
||||
|
||||
private boolean addressNotMatches(URL url, List<String> addresses) {
|
||||
private boolean addressNotMatches(URL url, Set<String> addresses) {
|
||||
return addresses == null || !checkAddressMatch(addresses, url.getHost(), url.getPort());
|
||||
}
|
||||
|
||||
private boolean checkAddressMatch(List<String> addresses, String host, int port) {
|
||||
private boolean checkAddressMatch(Set<String> addresses, String host, int port) {
|
||||
for (String address : addresses) {
|
||||
try {
|
||||
if (NetUtils.matchIpExpression(address, host, port)) {
|
||||
|
|
@ -237,7 +242,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(CLUSTER_TAG_ROUTE_INVALID,"tag route address is invalid","","The format of ip address is invalid in tag route. Address :" + address,e);
|
||||
logger.error(CLUSTER_TAG_ROUTE_INVALID, "tag route address is invalid", "", "The format of ip address is invalid in tag route. Address :" + address, e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
@ -249,6 +254,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
|
||||
@Override
|
||||
public void notify(BitList<Invoker<T>> invokers) {
|
||||
this.invokers = invokers;
|
||||
if (CollectionUtils.isEmpty(invokers)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -275,14 +281,29 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
|
|||
if (StringUtils.isNotEmpty(rawRule)) {
|
||||
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
|
||||
}
|
||||
} else {
|
||||
if (this.tagRouterRule != null) {
|
||||
TagRouterRule newRule = TagRuleParser.parse(this.tagRouterRule.getRawRule());
|
||||
newRule.init(this);
|
||||
this.tagRouterRule = newRule;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BitList<Invoker<T>> getInvokers() {
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (StringUtils.isNotEmpty(application)) {
|
||||
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
|
||||
}
|
||||
}
|
||||
|
||||
// for testing purpose
|
||||
public void setTagRouterRule(TagRouterRule tagRouterRule) {
|
||||
this.tagRouterRule = tagRouterRule;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.tag.model;
|
||||
|
||||
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
|
||||
|
||||
public class ParamMatch {
|
||||
private String key;
|
||||
private StringMatch value;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public StringMatch getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(StringMatch value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isMatch(String input) {
|
||||
if (getValue() != null && input != null) {
|
||||
return getValue().isMatch(input);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,19 +16,44 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.router.tag.model;
|
||||
|
||||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.PojoUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RULE_PARSING;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
|
||||
|
||||
public class Tag {
|
||||
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(Tag.class);
|
||||
|
||||
private String name;
|
||||
private List<ParamMatch> match;
|
||||
private List<String> addresses;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Tag parseFromMap(Map<String, Object> map) {
|
||||
public static Tag parseFromMap(Map<String, Object> map, String version) {
|
||||
Tag tag = new Tag();
|
||||
tag.setName((String) map.get("name"));
|
||||
|
||||
if (version != null && version.startsWith(RULE_VERSION_V30)) {
|
||||
if (map.get("match") != null) {
|
||||
tag.setMatch(((List<Map<String, Object>>) map.get("match")).stream().map((objectMap) -> {
|
||||
try {
|
||||
return PojoUtils.mapToPojo(objectMap, ParamMatch.class);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
logger.error(CLUSTER_FAILED_RULE_PARSING, " Failed to parse tag rule ", String.valueOf(objectMap), "Error occurred when parsing rule component.", e);
|
||||
}
|
||||
return null;
|
||||
}).collect(Collectors.toList()));
|
||||
} else {
|
||||
logger.warn(CLUSTER_FAILED_RULE_PARSING, "", String.valueOf(map), "It's recommended to use 'match' instead of 'addresses' for v3.0 tag rule.");
|
||||
}
|
||||
}
|
||||
|
||||
Object addresses = map.get("addresses");
|
||||
if (addresses != null && List.class.isAssignableFrom(addresses.getClass())) {
|
||||
tag.setAddresses(((List<Object>) addresses).stream().map(String::valueOf).collect(Collectors.toList()));
|
||||
|
|
@ -52,4 +77,13 @@ public class Tag {
|
|||
public void setAddresses(List<String> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public List<ParamMatch> getMatch() {
|
||||
return match;
|
||||
}
|
||||
|
||||
public void setMatch(List<ParamMatch> match) {
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,19 @@
|
|||
package org.apache.dubbo.rpc.cluster.router.tag.model;
|
||||
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
import org.apache.dubbo.rpc.cluster.router.tag.TagStateRouter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.TAGS_KEY;
|
||||
|
||||
/**
|
||||
|
|
@ -45,8 +50,8 @@ import static org.apache.dubbo.rpc.cluster.Constants.TAGS_KEY;
|
|||
public class TagRouterRule extends AbstractRouterRule {
|
||||
private List<Tag> tags;
|
||||
|
||||
private final Map<String, List<String>> addressToTagnames = new HashMap<>();
|
||||
private final Map<String, List<String>> tagnameToAddresses = new HashMap<>();
|
||||
private final Map<String, Set<String>> addressToTagnames = new HashMap<>();
|
||||
private final Map<String, Set<String>> tagnameToAddresses = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static TagRouterRule parseFromMap(Map<String, Object> map) {
|
||||
|
|
@ -56,43 +61,71 @@ public class TagRouterRule extends AbstractRouterRule {
|
|||
Object tags = map.get(TAGS_KEY);
|
||||
if (tags != null && List.class.isAssignableFrom(tags.getClass())) {
|
||||
tagRouterRule.setTags(((List<Map<String, Object>>) tags).stream()
|
||||
.map(Tag::parseFromMap).collect(Collectors.toList()));
|
||||
.map(objMap -> Tag.parseFromMap(objMap, tagRouterRule.getVersion())).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
return tagRouterRule;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
public void init(TagStateRouter<?> router) {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BitList<? extends Invoker<?>> invokers = router.getInvokers();
|
||||
|
||||
// for tags with 'addresses` field set and 'match' field not set
|
||||
tags.stream().filter(tag -> CollectionUtils.isNotEmpty(tag.getAddresses())).forEach(tag -> {
|
||||
tagnameToAddresses.put(tag.getName(), tag.getAddresses());
|
||||
tagnameToAddresses.put(tag.getName(), new HashSet<>(tag.getAddresses()));
|
||||
tag.getAddresses().forEach(addr -> {
|
||||
List<String> tagNames = addressToTagnames.computeIfAbsent(addr, k -> new ArrayList<>());
|
||||
Set<String> tagNames = addressToTagnames.computeIfAbsent(addr, k -> new HashSet<>());
|
||||
tagNames.add(tag.getName());
|
||||
});
|
||||
});
|
||||
|
||||
if (this.getVersion() != null && this.getVersion().startsWith(RULE_VERSION_V30)) {
|
||||
// for tags with 'match` field set and 'addresses' field not set
|
||||
if (CollectionUtils.isNotEmpty(invokers)) {
|
||||
tags.stream().filter(tag -> CollectionUtils.isEmpty(tag.getAddresses())).forEach(tag -> {
|
||||
Set<String> addresses = new HashSet<>();
|
||||
List<ParamMatch> paramMatchers = tag.getMatch();
|
||||
invokers.forEach(invoker -> {
|
||||
boolean isMatch = true;
|
||||
for (ParamMatch matcher : paramMatchers) {
|
||||
if (!matcher.isMatch(invoker.getUrl().getOriginalParameter(matcher.getKey()))) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isMatch) {
|
||||
addresses.add(invoker.getUrl().getAddress());
|
||||
}
|
||||
});
|
||||
if (CollectionUtils.isNotEmpty(addresses)) {// null means tag not set
|
||||
tagnameToAddresses.put(tag.getName(), addresses);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getAddresses() {
|
||||
return tags.stream()
|
||||
.filter(tag -> CollectionUtils.isNotEmpty(tag.getAddresses()))
|
||||
.flatMap(tag -> tag.getAddresses().stream())
|
||||
.collect(Collectors.toList());
|
||||
public Set<String> getAddresses() {
|
||||
return tagnameToAddresses.entrySet().stream()
|
||||
.filter(entry -> CollectionUtils.isNotEmpty(entry.getValue()))
|
||||
.flatMap(entry -> entry.getValue().stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public List<String> getTagNames() {
|
||||
return tags.stream().map(Tag::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getAddressToTagnames() {
|
||||
public Map<String, Set<String>> getAddressToTagnames() {
|
||||
return addressToTagnames;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, List<String>> getTagnameToAddresses() {
|
||||
public Map<String, Set<String>> getTagnameToAddresses() {
|
||||
return tagnameToAddresses;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* Parse raw rule into structured tag rule
|
||||
*/
|
||||
public class TagRuleParser {
|
||||
|
||||
|
|
@ -37,7 +37,6 @@ public class TagRuleParser {
|
|||
rule.setValid(false);
|
||||
}
|
||||
|
||||
rule.init();
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
|
|||
|
||||
private volatile boolean enableConnectivityValidation = true;
|
||||
|
||||
private AtomicBoolean destroyed = new AtomicBoolean(false);
|
||||
private final AtomicBoolean destroyed = new AtomicBoolean(false);
|
||||
|
||||
private volatile Invoker<T> stickyInvoker = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER
|
|||
public abstract class AbstractCluster implements Cluster {
|
||||
|
||||
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker) {
|
||||
// AbstractClusterInvoker<T> last = clusterInvoker;
|
||||
AbstractClusterInvoker<T> last = buildInterceptorInvoker(new ClusterFilterInvoker<>(clusterInvoker));
|
||||
|
||||
if (Boolean.parseBoolean(ConfigurationUtils.getProperty(clusterInvoker.getDirectory().getConsumerUrl().getScopeModel(), CLUSTER_INTERCEPTOR_COMPATIBLE_KEY, "false"))) {
|
||||
|
|
@ -72,7 +71,7 @@ public abstract class AbstractCluster implements Cluster {
|
|||
protected abstract <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException;
|
||||
|
||||
static class ClusterFilterInvoker<T> extends AbstractClusterInvoker<T> {
|
||||
private ClusterInvoker<T> filterInvoker;
|
||||
private final ClusterInvoker<T> filterInvoker;
|
||||
|
||||
public ClusterFilterInvoker(AbstractClusterInvoker<T> invoker) {
|
||||
List<FilterChainBuilder> builders = ScopeModelUtil.getApplicationModel(invoker.getUrl().getScopeModel()).getExtensionLoader(FilterChainBuilder.class).getActivateExtensions();
|
||||
|
|
@ -191,9 +190,9 @@ public abstract class AbstractCluster implements Cluster {
|
|||
@Deprecated
|
||||
static class InterceptorInvokerNode<T> extends AbstractClusterInvoker<T> {
|
||||
|
||||
private AbstractClusterInvoker<T> clusterInvoker;
|
||||
private ClusterInterceptor interceptor;
|
||||
private AbstractClusterInvoker<T> next;
|
||||
private final AbstractClusterInvoker<T> clusterInvoker;
|
||||
private final ClusterInterceptor interceptor;
|
||||
private final AbstractClusterInvoker<T> next;
|
||||
|
||||
public InterceptorInvokerNode(AbstractClusterInvoker<T> clusterInvoker,
|
||||
ClusterInterceptor interceptor,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.support.wrapper;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.url.component.ServiceConfigURL;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.rpc.Exporter;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Protocol;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.Cluster;
|
||||
import org.apache.dubbo.rpc.cluster.ClusterInvoker;
|
||||
import org.apache.dubbo.rpc.cluster.Directory;
|
||||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.listener.ExporterChangeListener;
|
||||
import org.apache.dubbo.rpc.listener.InjvmExporterListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
|
||||
import static org.apache.dubbo.rpc.Constants.LOCAL_KEY;
|
||||
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
|
||||
import static org.apache.dubbo.rpc.Constants.SCOPE_KEY;
|
||||
import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
|
||||
import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.PEER_KEY;
|
||||
|
||||
/**
|
||||
* A ClusterInvoker that selects between local and remote invokers at runtime.
|
||||
*/
|
||||
public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChangeListener {
|
||||
|
||||
private Protocol protocolSPI;
|
||||
private final Directory<T> directory;
|
||||
private final Invoker<T> invoker;
|
||||
private final AtomicBoolean isExported;
|
||||
private volatile Invoker<T> injvmInvoker;
|
||||
private volatile InjvmExporterListener injvmExporterListener;
|
||||
|
||||
private boolean peerFlag;
|
||||
|
||||
private boolean injvmFlag;
|
||||
|
||||
private final Object createLock = new Object();
|
||||
|
||||
|
||||
public ScopeClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
|
||||
this.directory = directory;
|
||||
this.invoker = invoker;
|
||||
this.isExported = new AtomicBoolean(false);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
Boolean peer = (Boolean) getUrl().getAttribute(PEER_KEY);
|
||||
String isInjvm = getUrl().getParameter(LOCAL_PROTOCOL);
|
||||
if (peer != null && peer) {
|
||||
peerFlag = true;
|
||||
return;
|
||||
}
|
||||
if (injvmInvoker == null && LOCAL_PROTOCOL.equalsIgnoreCase(getRegistryUrl().getProtocol())) {
|
||||
injvmInvoker = invoker;
|
||||
isExported.compareAndSet(false, true);
|
||||
injvmFlag = true;
|
||||
return;
|
||||
}
|
||||
if (Boolean.TRUE.toString().equalsIgnoreCase(isInjvm) || LOCAL_KEY.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY))) {
|
||||
injvmFlag = true;
|
||||
} else if (isInjvm == null) {
|
||||
injvmFlag = isNotRemoteOrGeneric();
|
||||
}
|
||||
|
||||
protocolSPI = getUrl().getApplicationModel().getExtensionLoader(Protocol.class).getAdaptiveExtension();
|
||||
injvmExporterListener = getUrl().getOrDefaultFrameworkModel().getBeanFactory().getBean(InjvmExporterListener.class);
|
||||
injvmExporterListener.addExporterChangeListener(this, getUrl().getServiceKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return directory.getConsumerUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getRegistryUrl() {
|
||||
return directory.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<T> getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return directory.isDestroyed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isExported.get() || directory.isAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (injvmExporterListener != null) {
|
||||
injvmExporterListener.removeExporterChangeListener(this, getUrl().getServiceKey());
|
||||
}
|
||||
destroyInjvmInvoker();
|
||||
this.invoker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getInterface() {
|
||||
return directory.getInterface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invocation invocation) throws RpcException {
|
||||
if (peerFlag) {
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
if (isInjvmExported()) {
|
||||
return injvmInvoker.invoke(invocation);
|
||||
}
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
|
||||
private boolean isNotRemoteOrGeneric() {
|
||||
return !SCOPE_REMOTE.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) &&
|
||||
!getUrl().getParameter(GENERIC_KEY, false);
|
||||
}
|
||||
|
||||
private boolean isInjvmExported() {
|
||||
Boolean localInvoke = RpcContext.getServiceContext().getLocalInvoke();
|
||||
boolean isExportedValue = isExported.get();
|
||||
boolean local = (localInvoke != null && localInvoke);
|
||||
// Determine whether this call is local
|
||||
if (isExportedValue && local) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine whether this call is remote
|
||||
if (localInvoke != null && !localInvoke) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When calling locally, determine whether it does not meet the requirements
|
||||
if (!isExportedValue && (SCOPE_LOCAL.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) ||
|
||||
Boolean.TRUE.toString().equalsIgnoreCase(getUrl().getParameter(LOCAL_PROTOCOL))|| local)) {
|
||||
throw new RpcException("Local service has not been exposed yet!");
|
||||
}
|
||||
|
||||
return isExportedValue && injvmFlag;
|
||||
}
|
||||
|
||||
|
||||
private void createInjvmInvoker() {
|
||||
if (injvmInvoker == null) {
|
||||
synchronized (createLock) {
|
||||
if (injvmInvoker == null) {
|
||||
URL url = new ServiceConfigURL(LOCAL_PROTOCOL, NetUtils.getLocalHost(), getUrl().getPort(), getInterface().getName(), getUrl().getParameters());
|
||||
url = url.setScopeModel(getUrl().getScopeModel());
|
||||
url = url.setServiceModel(getUrl().getServiceModel());
|
||||
Invoker<?> invoker = protocolSPI.refer(getInterface(), url);
|
||||
List<Invoker<?>> invokers = new ArrayList<>();
|
||||
invokers.add(invoker);
|
||||
injvmInvoker = Cluster.getCluster(url.getScopeModel(), Cluster.DEFAULT, false).join(new StaticDirectory(url, invokers), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExporterChangeExport(Exporter<?> exporter) {
|
||||
if (isExported.get()) {
|
||||
return;
|
||||
}
|
||||
if (getUrl().getServiceKey().equals(exporter.getInvoker().getUrl().getServiceKey())
|
||||
&& exporter.getInvoker().getUrl().getProtocol().equalsIgnoreCase(LOCAL_PROTOCOL)) {
|
||||
createInjvmInvoker();
|
||||
isExported.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExporterChangeUnExport(Exporter<?> exporter) {
|
||||
if (getUrl().getServiceKey().equals(exporter.getInvoker().getUrl().getServiceKey())
|
||||
&& exporter.getInvoker().getUrl().getProtocol().equalsIgnoreCase(LOCAL_PROTOCOL)) {
|
||||
destroyInjvmInvoker();
|
||||
isExported.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyInjvmInvoker() {
|
||||
if (injvmInvoker != null) {
|
||||
injvmInvoker.destroy();
|
||||
injvmInvoker = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Invoker<?> getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.support.wrapper;
|
||||
|
||||
import org.apache.dubbo.common.extension.Wrapper;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.cluster.Cluster;
|
||||
import org.apache.dubbo.rpc.cluster.Directory;
|
||||
|
||||
/**
|
||||
* Introducing ScopeClusterInvoker section through Dubbo SPI mechanism
|
||||
*/
|
||||
@Wrapper(order = -1)
|
||||
public class ScopeClusterWrapper implements Cluster {
|
||||
private final Cluster cluster;
|
||||
|
||||
public ScopeClusterWrapper(Cluster cluster) {
|
||||
this.cluster = cluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Invoker<T> join(Directory<T> directory, boolean buildFilterChain) throws RpcException {
|
||||
return new ScopeClusterInvoker<>(directory,
|
||||
this.cluster.join(directory, buildFilterChain));
|
||||
}
|
||||
|
||||
public Cluster getCluster() {
|
||||
return cluster;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
|
||||
scope=org.apache.dubbo.rpc.cluster.support.wrapper.ScopeClusterWrapper
|
||||
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
|
||||
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
|
||||
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
|
||||
|
|
@ -7,4 +8,4 @@ forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
|
|||
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
|
||||
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
|
||||
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
|
||||
zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster
|
||||
zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
attachment=org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment.AttachmentConditionMatcherFactory
|
||||
argument=org.apache.dubbo.rpc.cluster.router.condition.matcher.argument.ArgumentConditionMatcherFactory
|
||||
param=org.apache.dubbo.rpc.cluster.router.condition.matcher.param.UrlParamConditionMatcherFactory
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
range=org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.range.RangeValuePattern
|
||||
wildcard=org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.wildcard.WildcardValuePattern
|
||||
|
|
@ -2,5 +2,7 @@ tag=org.apache.dubbo.rpc.cluster.router.tag.TagStateRouterFactory
|
|||
condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionStateRouterFactory
|
||||
service=org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouterFactory
|
||||
app=org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouterFactory
|
||||
provider-app=org.apache.dubbo.rpc.cluster.router.condition.config.ProviderAppStateRouterFactory
|
||||
script-app=org.apache.dubbo.rpc.cluster.router.script.config.AppScriptRouterFactory
|
||||
mock=org.apache.dubbo.rpc.cluster.router.mock.MockStateRouterFactory
|
||||
standard-mesh-rule=org.apache.dubbo.rpc.cluster.router.mesh.route.StandardMeshRuleRouterFactory
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import java.util.Optional;
|
|||
class ConfiguratorTest {
|
||||
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
void test() {
|
||||
|
||||
Optional<List<Configurator>> emptyOptional = Configurator.toConfigurators(Collections.emptyList());
|
||||
Assertions.assertEquals(Optional.empty(), emptyOptional);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class RouterChainTest {
|
|||
void testBuildRouterChain() {
|
||||
RouterChain<DemoService> routerChain = createRouterChanin();
|
||||
Assertions.assertEquals(0, routerChain.getRouters().size());
|
||||
Assertions.assertEquals(5, routerChain.getStateRouters().size());
|
||||
Assertions.assertEquals(7, routerChain.getStateRouters().size());
|
||||
}
|
||||
|
||||
private RouterChain<DemoService> createRouterChanin() {
|
||||
|
|
@ -158,14 +158,16 @@ class RouterChainTest {
|
|||
Assertions.assertTrue(result.contains(invoker5));
|
||||
|
||||
String snapshotLog =
|
||||
"[ Parent (Input: 6) (Current Node Output: 6) (Chain Node Output: 1) ] Input: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103 -> Chain Node Output: localhost:9103...\n" +
|
||||
"[ Parent (Input: 6) (Current Node Output: 6) (Chain Node Output: 1) ] Input: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103 -> Chain Node Output: localhost:9103...\n" +
|
||||
" [ MockInvokersSelector (Input: 6) (Current Node Output: 5) (Chain Node Output: 1) Router message: invocation.need.mock not set. Return normal Invokers. ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
|
||||
" [ StandardMeshRuleRouter (Input: 5) (Current Node Output: 4) (Chain Node Output: 1) Router message: Match App: app Subset: isolation ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
|
||||
" [ TagStateRouter (Input: 4) (Current Node Output: 3) (Chain Node Output: 1) Router message: Disable Tag Router. Reason: tagRouterRule is invalid or disabled ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
|
||||
" [ ServiceStateRouter (Input: 3) (Current Node Output: 3) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
|
||||
" [ ConditionStateRouter (Input: 3) (Current Node Output: 2) (Chain Node Output: 2) Router message: Match return. ] Current Node Output: localhost:9103,localhost:9103\n" +
|
||||
" [ AppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103\n" +
|
||||
" [ ConditionStateRouter (Input: 2) (Current Node Output: 1) (Chain Node Output: 1) Router message: Match return. ] Current Node Output: localhost:9103";
|
||||
" [ ProviderAppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: Directly return. Reason: Invokers from previous router is empty or conditionRouters is empty. ] Current Node Output: localhost:9103,localhost:9103\n" +
|
||||
" [ AppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103\n" +
|
||||
" [ ConditionStateRouter (Input: 2) (Current Node Output: 1) (Chain Node Output: 1) Router message: Match return. ] Current Node Output: localhost:9103\n" +
|
||||
" [ AppScriptStateRouter (Input: 1) (Current Node Output: 1) (Chain Node Output: 1) Router message: Directly return from script router. Reason: Invokers from previous router is empty or script is not enabled. Script rule is: null ] Current Node Output: localhost:9103";
|
||||
String[] snapshot = routerSnapshotSwitcher.cloneSnapshot();
|
||||
Assertions.assertTrue(snapshot[0].contains(snapshotLog));
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,19 @@ import org.apache.dubbo.common.URL;
|
|||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.absent.AbsentConfigurator;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.consts.UrlConstant;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ParamMatch;
|
||||
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
|
||||
|
||||
/**
|
||||
* OverrideConfiguratorTest
|
||||
*/
|
||||
|
|
@ -80,6 +86,7 @@ class OverrideConfiguratorTest {
|
|||
params.put("configVersion", "2.7");
|
||||
params.put("application", "foo");
|
||||
params.put("timeout", "10000");
|
||||
|
||||
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
|
||||
|
||||
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
|
||||
|
|
@ -114,4 +121,76 @@ class OverrideConfiguratorTest {
|
|||
|
||||
}
|
||||
|
||||
// Test the version after 2.7
|
||||
@Test
|
||||
void testOverrideForVersion3() {
|
||||
// match
|
||||
{
|
||||
String consumerUrlV3 = "dubbo://172.24.160.179/com.foo.BarService?match_key=value&application=foo&side=consumer&timeout=100";
|
||||
|
||||
URL consumerConfiguratorUrl = URL.valueOf("override://0.0.0.0/com.foo.BarService");
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("side", "consumer");
|
||||
params.put("configVersion", "v3.0");
|
||||
params.put("application", "foo");
|
||||
params.put("timeout", "10000");
|
||||
|
||||
ConditionMatch matcher = new ConditionMatch();
|
||||
ParamMatch paramMatch = new ParamMatch();
|
||||
paramMatch.setKey("match_key");
|
||||
StringMatch stringMatch = new StringMatch();
|
||||
stringMatch.setExact("value");
|
||||
paramMatch.setValue(stringMatch);
|
||||
matcher.setParam(Arrays.asList(paramMatch));
|
||||
consumerConfiguratorUrl = consumerConfiguratorUrl.putAttribute(MATCH_CONDITION, matcher);
|
||||
|
||||
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
|
||||
|
||||
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
|
||||
// Meet the configured conditions:
|
||||
// same side
|
||||
// The port of configuratorUrl is 0
|
||||
// The host of configuratorUrl is 0.0.0.0 or the local address is the same as consumerUrlV27
|
||||
// same appName
|
||||
URL originalURL = URL.valueOf(consumerUrlV3);
|
||||
Assertions.assertEquals("100", originalURL.getParameter("timeout"));
|
||||
URL url = configurator.configure(originalURL);
|
||||
Assertions.assertEquals("10000", url.getParameter("timeout"));
|
||||
}
|
||||
|
||||
// mismatch
|
||||
{
|
||||
String consumerUrlV3 = "dubbo://172.24.160.179/com.foo.BarService?match_key=value&application=foo&side=consumer&timeout=100";
|
||||
|
||||
URL consumerConfiguratorUrl = URL.valueOf("override://0.0.0.0/com.foo.BarService");
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("side", "consumer");
|
||||
params.put("configVersion", "v3.0");
|
||||
params.put("application", "foo");
|
||||
params.put("timeout", "10000");
|
||||
|
||||
ConditionMatch matcher = new ConditionMatch();
|
||||
ParamMatch paramMatch = new ParamMatch();
|
||||
paramMatch.setKey("match_key");
|
||||
StringMatch stringMatch = new StringMatch();
|
||||
stringMatch.setExact("not_match_value");
|
||||
paramMatch.setValue(stringMatch);
|
||||
matcher.setParam(Arrays.asList(paramMatch));
|
||||
consumerConfiguratorUrl = consumerConfiguratorUrl.putAttribute(MATCH_CONDITION, matcher);
|
||||
|
||||
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
|
||||
|
||||
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
|
||||
// Meet the configured conditions:
|
||||
// same side
|
||||
// The port of configuratorUrl is 0
|
||||
// The host of configuratorUrl is 0.0.0.0 or the local address is the same as consumerUrlV27
|
||||
// same appName
|
||||
URL originalURL = URL.valueOf(consumerUrlV3);
|
||||
Assertions.assertEquals("100", originalURL.getParameter("timeout"));
|
||||
URL url = configurator.configure(originalURL);
|
||||
Assertions.assertEquals("100", url.getParameter("timeout"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.dubbo.rpc.cluster.configurator.parser;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
|
||||
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -33,6 +34,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;
|
|||
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -165,6 +167,75 @@ class ConfigParserTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseProviderConfigurationV3() throws IOException {
|
||||
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3.yml")) {
|
||||
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
|
||||
Assertions.assertNotNull(urls);
|
||||
Assertions.assertEquals(1, urls.size());
|
||||
URL url = urls.get(0);
|
||||
Assertions.assertEquals("0.0.0.0", url.getAddress());
|
||||
Assertions.assertEquals("*", url.getServiceInterface());
|
||||
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
|
||||
Assertions.assertEquals("demo-provider", url.getApplication());
|
||||
|
||||
URL matchURL1 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
|
||||
URL matchURL2 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService2?match_key1=value1");
|
||||
URL notMatchURL1 = URL.valueOf("dubbo://10.0.0.2:20880/DemoService?match_key1=value1");// address not match
|
||||
URL notMatchURL2 = URL.valueOf("dubbo://10.0.0.1:20880/DemoServiceNotMatch?match_key1=value1");// service not match
|
||||
URL notMatchURL3 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
|
||||
|
||||
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
|
||||
Assertions.assertTrue(matcher.isMatch(matchURL1));
|
||||
Assertions.assertTrue(matcher.isMatch(matchURL2));
|
||||
Assertions.assertFalse(matcher.isMatch(notMatchURL1));
|
||||
Assertions.assertFalse(matcher.isMatch(notMatchURL2));
|
||||
Assertions.assertFalse(matcher.isMatch(notMatchURL3));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseProviderConfigurationV3Compatibility() throws IOException {
|
||||
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3Compatibility.yml")) {
|
||||
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
|
||||
Assertions.assertNotNull(urls);
|
||||
Assertions.assertEquals(1, urls.size());
|
||||
URL url = urls.get(0);
|
||||
Assertions.assertEquals("10.0.0.1:20880", url.getAddress());
|
||||
Assertions.assertEquals("DemoService", url.getServiceInterface());
|
||||
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
|
||||
Assertions.assertEquals("demo-provider", url.getApplication());
|
||||
|
||||
URL matchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
|
||||
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
|
||||
|
||||
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
|
||||
Assertions.assertTrue(matcher.isMatch(matchURL));
|
||||
Assertions.assertFalse(matcher.isMatch(notMatchURL));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseProviderConfigurationV3Conflict() throws IOException {
|
||||
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3Duplicate.yml")) {
|
||||
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
|
||||
Assertions.assertNotNull(urls);
|
||||
Assertions.assertEquals(1, urls.size());
|
||||
URL url = urls.get(0);
|
||||
Assertions.assertEquals("10.0.0.1:20880", url.getAddress());
|
||||
Assertions.assertEquals("DemoService", url.getServiceInterface());
|
||||
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
|
||||
Assertions.assertEquals("demo-provider", url.getApplication());
|
||||
|
||||
URL matchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
|
||||
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
|
||||
|
||||
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
|
||||
Assertions.assertTrue(matcher.isMatch(matchURL));
|
||||
Assertions.assertFalse(matcher.isMatch(notMatchURL));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseURLJsonArrayCompatible() {
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.dubbo.rpc.cluster.directory;
|
|||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.utils.Holder;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcInvocation;
|
||||
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
|
||||
|
|
@ -26,6 +27,7 @@ import org.apache.dubbo.rpc.cluster.router.condition.ConditionStateRouterFactory
|
|||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
|
||||
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -60,6 +62,7 @@ class StaticDirectoryTest {
|
|||
|
||||
|
||||
List<Invoker<String>> filteredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation(), false, new Holder<>());
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
StaticDirectory<String> staticDirectory = new StaticDirectory<>(filteredInvokers);
|
||||
boolean isAvailable = staticDirectory.isAvailable();
|
||||
Assertions.assertTrue(!isAvailable);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class DefaultFilterChainBuilderTest {
|
|||
urlWithoutFilter = urlWithoutFilter.setScopeModel(ApplicationModel.defaultModel());
|
||||
AbstractInvoker<DemoService> invokerWithoutFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithoutFilter) {
|
||||
@Override
|
||||
protected Result doInvoke(Invocation invocation) throws Throwable {
|
||||
protected Result doInvoke(Invocation invocation) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -58,7 +58,7 @@ class DefaultFilterChainBuilderTest {
|
|||
urlWithFilter = urlWithFilter.setScopeModel(ApplicationModel.defaultModel());
|
||||
AbstractInvoker<DemoService> invokerWithFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithFilter) {
|
||||
@Override
|
||||
protected Result doInvoke(Invocation invocation) throws Throwable {
|
||||
protected Result doInvoke(Invocation invocation) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -78,7 +78,7 @@ class DefaultFilterChainBuilderTest {
|
|||
urlWithoutFilter = urlWithoutFilter.setScopeModel(ApplicationModel.defaultModel());
|
||||
AbstractInvoker<DemoService> invokerWithoutFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithoutFilter) {
|
||||
@Override
|
||||
protected Result doInvoke(Invocation invocation) throws Throwable {
|
||||
protected Result doInvoke(Invocation invocation) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -93,7 +93,7 @@ class DefaultFilterChainBuilderTest {
|
|||
urlWithFilter = urlWithFilter.setScopeModel(ApplicationModel.defaultModel());
|
||||
AbstractInvoker<DemoService> invokerWithFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithFilter) {
|
||||
@Override
|
||||
protected Result doInvoke(Invocation invocation) throws Throwable {
|
||||
protected Result doInvoke(Invocation invocation) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,11 +23,17 @@ import org.apache.dubbo.rpc.model.FrameworkModel;
|
|||
import org.apache.dubbo.rpc.model.ServiceModel;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class RouterSnapshotFilterTest {
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
RpcContext.getServiceContext().setNeedPrintRouterSnapshot(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
FrameworkModel frameworkModel = new FrameworkModel();
|
||||
|
|
|
|||
|
|
@ -369,4 +369,124 @@ class ConditionStateRouterTest {
|
|||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(0, fileredInvokers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRoute_Attachments() {
|
||||
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[foo] = a " + " => " + " host = 1.2.3.4").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
List<Invoker<String>> originInvokers = new ArrayList<>();
|
||||
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?region=hangzhou"));
|
||||
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
invocation.setAttachment("foo", "a");
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(0, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments = a " + " => " + " host = 1.2.3.4").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[foo] = a " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(1, fileredInvokers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRoute_Range_Pattern() {
|
||||
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = 1~100 " + " => " + " region=hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
List<Invoker<String>> originInvokers = new ArrayList<>();
|
||||
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?region=hangzhou"));
|
||||
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
invocation.setAttachment("user_id", "80");
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(1, fileredInvokers.size());
|
||||
|
||||
invocation.setAttachment("user_id", "101");
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
invocation.setAttachment("user_id", "1");
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(1, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
invocation.setAttachment("user_id", "101");
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRoute_Key_Not_Exist() {
|
||||
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("when_key=a " + " => " + " not_exist_then_key = any_value").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
List<Invoker<String>> originInvokers = new ArrayList<>();
|
||||
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?then_key=a"));
|
||||
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
|
||||
URL consumer = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?when_key=a");
|
||||
|
||||
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), consumer, null, false, new Holder<>());
|
||||
Assertions.assertEquals(0, fileredInvokers.size());
|
||||
|
||||
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("not_exist_when_key=a " + " => " + " then_key = a").addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
fileredInvokers = router.route(invokers, consumer, null, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRoute_Multiple_Conditions() {
|
||||
List<Invoker<String>> originInvokers = new ArrayList<>();
|
||||
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService"));
|
||||
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
String p = "a";
|
||||
invocation.setArguments(new Object[]{p});
|
||||
|
||||
// all conditions match
|
||||
URL consumer1 = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?application=consumer_app");
|
||||
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("application=consumer_app&arguments[0]=a" + " => " + " host = " + LOCAL_HOST).addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), consumer1, invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(2, fileredInvokers.size());
|
||||
|
||||
// one of the conditions does not match
|
||||
URL consume2 = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?application=another_consumer_app");
|
||||
StateRouter router2 = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("application=consumer_app&arguments[0]=a" + " => " + " host = " + LOCAL_HOST).addParameter(FORCE_KEY, String.valueOf(true)));
|
||||
fileredInvokers = router2.route(invokers.clone(), consume2, invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, fileredInvokers.size());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.condition.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
|
||||
import org.apache.dubbo.common.utils.Holder;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcInvocation;
|
||||
import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
|
||||
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
|
||||
|
||||
public class ProviderAppConditionStateRouterTest {
|
||||
private static final String LOCAL_HOST = "127.0.0.1";
|
||||
private static final String RULE_SUFFIX = ".condition-router";
|
||||
|
||||
private static GovernanceRuleRepository ruleRepository;
|
||||
private URL url = URL.valueOf("consumer://1.1.1.1/com.foo.BarService");
|
||||
private String rawRule = "---\n" +
|
||||
"configVersion: v3.0\n" +
|
||||
"scope: application\n" +
|
||||
"force: true\n" +
|
||||
"runtime: false\n" +
|
||||
"enabled: true\n" +
|
||||
"priority: 1\n" +
|
||||
"key: demo-provider\n" +
|
||||
"conditions:\n" +
|
||||
"- method=sayHello => region=hangzhou\n" +
|
||||
"...";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
ruleRepository = Mockito.mock(GovernanceRuleRepository.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
ProviderAppStateRouter<String> router = new ProviderAppStateRouter<>(url);
|
||||
router = Mockito.spy(router);
|
||||
Mockito.when(router.getRuleRepository()).thenReturn(ruleRepository);
|
||||
Mockito.when(ruleRepository.getRule("demo-provider" + RULE_SUFFIX, DynamicConfiguration.DEFAULT_GROUP)).thenReturn(rawRule);
|
||||
// Mockito.when(ruleRepository.addListener()).thenReturn();
|
||||
|
||||
BitList<Invoker<String>> invokers = getInvokers();
|
||||
router.notify(invokers);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("sayHello");
|
||||
List<Invoker<String>> result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(1, result.size());
|
||||
|
||||
invocation.setMethodName("sayHi");
|
||||
result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(3, result.size());
|
||||
}
|
||||
|
||||
private BitList<Invoker<String>> getInvokers() {
|
||||
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
|
||||
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
|
||||
"dubbo://10.20.3.3:20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
|
||||
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
|
||||
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider®ion=hangzhou"));
|
||||
Invoker<String> invoker3 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
|
||||
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
return invokers;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.router.file;
|
|||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.AppResponse;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
|
|
@ -30,6 +31,7 @@ import org.apache.dubbo.rpc.cluster.LoadBalance;
|
|||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -59,6 +61,7 @@ class FileRouterEngineTest {
|
|||
|
||||
@BeforeAll
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
System.setProperty(ENABLE_CONNECTIVITY_VALIDATION, "false");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
|
@ -51,7 +50,11 @@ class MeshRuleCacheTest {
|
|||
@Test
|
||||
void containMapKeyValue() {
|
||||
URL url = mock(URL.class);
|
||||
when(url.getServiceKey()).thenReturn("test");
|
||||
when(url.getOriginalServiceParameter("test", "key1")).thenReturn("value1");
|
||||
when(url.getOriginalServiceParameter("test", "key2")).thenReturn("value2");
|
||||
when(url.getOriginalServiceParameter("test", "key3")).thenReturn("value3");
|
||||
when(url.getOriginalServiceParameter("test", "key4")).thenReturn("value4");
|
||||
|
||||
|
||||
Map<String, String> originMap = new HashMap<>();
|
||||
|
||||
|
|
@ -64,14 +67,10 @@ class MeshRuleCacheTest {
|
|||
inputMap.put("key1", "value1");
|
||||
inputMap.put("key2", "value2");
|
||||
|
||||
assertTrue(MeshRuleCache.containMapKeyValue(originMap, inputMap));
|
||||
assertTrue(MeshRuleCache.isLabelMatch(url, "test", inputMap));
|
||||
|
||||
inputMap.put("key4", "value4");
|
||||
assertFalse(MeshRuleCache.containMapKeyValue(originMap, inputMap));
|
||||
|
||||
|
||||
assertTrue(MeshRuleCache.containMapKeyValue(originMap, null));
|
||||
assertTrue(MeshRuleCache.containMapKeyValue(originMap, new HashMap<>()));
|
||||
assertTrue(MeshRuleCache.isLabelMatch(url, "test", inputMap));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.router.script.config;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
|
||||
import org.apache.dubbo.common.utils.Holder;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.RpcInvocation;
|
||||
import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
|
||||
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
|
||||
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public class AppScriptStateRouterTest {
|
||||
private static final String LOCAL_HOST = "127.0.0.1";
|
||||
private static final String RULE_SUFFIX = ".script-router";
|
||||
|
||||
private static GovernanceRuleRepository ruleRepository;
|
||||
private URL url = URL.valueOf("dubbo://1.1.1.1/com.foo.BarService");
|
||||
private String rawRule = "---\n" +
|
||||
"configVersion: v3.0\n" +
|
||||
"key: demo-provider\n" +
|
||||
"type: javascript\n" +
|
||||
"script: |\n" +
|
||||
" (function route(invokers,invocation,context) {\n" +
|
||||
" var result = new java.util.ArrayList(invokers.size());\n" +
|
||||
" for (i = 0; i < invokers.size(); i ++) {\n" +
|
||||
" if (\"10.20.3.3\".equals(invokers.get(i).getUrl().getHost())) {\n" +
|
||||
" result.add(invokers.get(i));\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" return result;\n" +
|
||||
" } (invokers, invocation, context)); // 表示立即执行方法\n" +
|
||||
"...";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
ruleRepository = Mockito.mock(GovernanceRuleRepository.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigScriptRoute() {
|
||||
AppScriptStateRouter<String> router = new AppScriptStateRouter<>(url);
|
||||
router = Mockito.spy(router);
|
||||
Mockito.when(router.getRuleRepository()).thenReturn(ruleRepository);
|
||||
Mockito.when(ruleRepository.getRule("demo-provider" + RULE_SUFFIX, DynamicConfiguration.DEFAULT_GROUP)).thenReturn(rawRule);
|
||||
// Mockito.when(ruleRepository.addListener()).thenReturn();
|
||||
|
||||
BitList<Invoker<String>> invokers = getInvokers();
|
||||
router.notify(invokers);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("sayHello");
|
||||
List<Invoker<String>> result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(1, result.size());
|
||||
}
|
||||
|
||||
private BitList<Invoker<String>> getInvokers() {
|
||||
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
|
||||
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
|
||||
"dubbo://10.20.3.3:20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
|
||||
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
|
||||
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
|
||||
Invoker<String> invoker3 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
|
||||
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
return invokers;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -94,6 +94,35 @@ class TagStateRouterTest {
|
|||
Assertions.assertEquals(invoker1, filteredInvokers.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTagRouteWithDynamicRuleV3() {
|
||||
TagStateRouter router = (TagStateRouter) new TagStateRouterFactory().getRouter(TagRouterRule.class, url);
|
||||
router = Mockito.spy(router);
|
||||
|
||||
List<Invoker<String>> originInvokers = new ArrayList<>();
|
||||
|
||||
URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?application=foo&dubbo.tag=tag2&match_key=value").setScopeModel(moduleModel);
|
||||
URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?application=foo&match_key=value").setScopeModel(moduleModel);
|
||||
URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?application=foo").setScopeModel(moduleModel);
|
||||
Invoker<String> invoker1 = new MockInvoker<>(url1, true);
|
||||
Invoker<String> invoker2 = new MockInvoker<>(url2, true);
|
||||
Invoker<String> invoker3 = new MockInvoker<>(url3, true);
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
originInvokers.add(invoker3);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setAttachment(TAG_KEY, "tag2");
|
||||
TagRouterRule rule = getTagRule();
|
||||
Mockito.when(router.getInvokers()).thenReturn(invokers);
|
||||
rule.init(router);
|
||||
router.setTagRouterRule(rule);
|
||||
List<Invoker<String>> filteredInvokers = router.route(invokers, invokers.get(0).getUrl(), invocation, false, new Holder<>());
|
||||
Assertions.assertEquals(2, filteredInvokers.size());
|
||||
// Assertions.(invoker1, filteredInvokers.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* TagRouterRule parse test when the tags addresses is null
|
||||
*
|
||||
|
|
@ -122,6 +151,9 @@ class TagStateRouterTest {
|
|||
"...";
|
||||
|
||||
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
|
||||
TagStateRouter<?> router = Mockito.mock(TagStateRouter.class);
|
||||
Mockito.when(router.getInvokers()).thenReturn(BitList.emptyList());
|
||||
tagRouterRule.init(router);
|
||||
|
||||
// assert tags
|
||||
assert tagRouterRule.getKey().equals("demo-provider");
|
||||
|
|
@ -138,4 +170,99 @@ class TagStateRouterTest {
|
|||
assert tagRouterRule.getTagnameToAddresses().get("tag4") == null;
|
||||
assert tagRouterRule.getAddresses().size() == 1;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void tagRouterRuleParseTestV3() {
|
||||
String tagRouterRuleConfig = "---\n" +
|
||||
"configVersion: v3.0\n" +
|
||||
"force: false\n" +
|
||||
"runtime: true\n" +
|
||||
"enabled: true\n" +
|
||||
"priority: 1\n" +
|
||||
"key: demo-provider\n" +
|
||||
"tags:\n" +
|
||||
" - name: tag1\n" +
|
||||
" match:\n" +
|
||||
" - key: match_key1\n" +
|
||||
" value:\n" +
|
||||
" exact: value1\n" +
|
||||
" - name: tag2\n" +
|
||||
" addresses:\n" +
|
||||
" - \"10.20.3.3:20880\"\n" +
|
||||
" - \"10.20.3.4:20880\"\n" +
|
||||
" match:\n" +
|
||||
" - key: match_key2\n" +
|
||||
" value:\n" +
|
||||
" exact: value2\n" +
|
||||
" - name: tag3\n" +
|
||||
" match:\n" +
|
||||
" - key: match_key2\n" +
|
||||
" value:\n" +
|
||||
" exact: value2\n" +
|
||||
" - name: tag4\n" +
|
||||
" match:\n" +
|
||||
" - key: not_exist\n" +
|
||||
" value:\n" +
|
||||
" exact: not_exist\n" +
|
||||
" - name: tag5\n" +
|
||||
" match:\n" +
|
||||
" - key: match_key2\n" +
|
||||
" value:\n" +
|
||||
" wildcard: \"*\"\n" +
|
||||
"...";
|
||||
|
||||
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
|
||||
TagStateRouter<String> router = Mockito.mock(TagStateRouter.class);
|
||||
Mockito.when(router.getInvokers()).thenReturn(getInvokers());
|
||||
tagRouterRule.init(router);
|
||||
|
||||
// assert tags
|
||||
assert tagRouterRule.getKey().equals("demo-provider");
|
||||
assert tagRouterRule.getPriority() == 1;
|
||||
assert tagRouterRule.getTagNames().contains("tag1");
|
||||
assert tagRouterRule.getTagNames().contains("tag2");
|
||||
assert tagRouterRule.getTagNames().contains("tag3");
|
||||
assert tagRouterRule.getTagNames().contains("tag4");
|
||||
// assert addresses
|
||||
assert tagRouterRule.getAddresses().size() == 2;
|
||||
assert tagRouterRule.getAddresses().contains("10.20.3.3:20880");
|
||||
assert tagRouterRule.getTagnameToAddresses().get("tag1").size() == 2;
|
||||
assert tagRouterRule.getTagnameToAddresses().get("tag2").size() == 2;
|
||||
assert tagRouterRule.getTagnameToAddresses().get("tag3").size() == 1;
|
||||
assert tagRouterRule.getTagnameToAddresses().get("tag5").size() == 1;
|
||||
assert tagRouterRule.getTagnameToAddresses().get("tag4") == null;
|
||||
|
||||
}
|
||||
|
||||
public BitList<Invoker<String>> getInvokers() {
|
||||
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
|
||||
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
|
||||
"dubbo://10.20.3.3:20880/com.foo.BarService?match_key1=value1&match_key2=value2"));
|
||||
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://10.20.3.4:20880/com.foo.BarService?match_key1=value1"));
|
||||
originInvokers.add(invoker1);
|
||||
originInvokers.add(invoker2);
|
||||
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
|
||||
return invokers;
|
||||
}
|
||||
|
||||
private TagRouterRule getTagRule() {
|
||||
String tagRouterRuleConfig = "---\n" +
|
||||
"configVersion: v3.0\n" +
|
||||
"force: false\n" +
|
||||
"runtime: true\n" +
|
||||
"enabled: true\n" +
|
||||
"priority: 1\n" +
|
||||
"key: demo-provider\n" +
|
||||
"tags:\n" +
|
||||
" - name: tag2\n" +
|
||||
" match:\n" +
|
||||
" - key: match_key\n" +
|
||||
" value:\n" +
|
||||
" exact: value\n" +
|
||||
"...";
|
||||
|
||||
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
|
||||
return tagRouterRule;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,18 +16,11 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
|
|
@ -41,6 +34,8 @@ import org.apache.dubbo.rpc.cluster.filter.DemoService;
|
|||
import org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance;
|
||||
import org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance;
|
||||
import org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -50,6 +45,14 @@ import org.junit.jupiter.api.Disabled;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ENABLE_CONNECTIVITY_VALIDATION;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
|
||||
|
|
@ -104,6 +107,7 @@ class AbstractClusterInvokerTest {
|
|||
@SuppressWarnings({"unchecked"})
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("application", "abstractClusterInvokerTest");
|
||||
url = url.putAttribute(REFER_KEY, attributes);
|
||||
|
|
@ -191,7 +195,7 @@ class AbstractClusterInvokerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSelect_Invokersize0() throws Exception {
|
||||
void testSelect_Invokersize0() {
|
||||
LoadBalance l = cluster.initLoadBalance(invokers, invocation);
|
||||
Assertions.assertNotNull(l,"cluster.initLoadBalance returns null!");
|
||||
{
|
||||
|
|
@ -225,7 +229,7 @@ class AbstractClusterInvokerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSelect_Invokersize1() throws Exception {
|
||||
void testSelect_Invokersize1() {
|
||||
invokers.clear();
|
||||
invokers.add(invoker1);
|
||||
LoadBalance l = cluster.initLoadBalance(invokers, invocation);
|
||||
|
|
@ -235,7 +239,7 @@ class AbstractClusterInvokerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSelect_Invokersize2AndselectNotNull() throws Exception {
|
||||
void testSelect_Invokersize2AndselectNotNull() {
|
||||
invokers.clear();
|
||||
invokers.add(invoker2);
|
||||
invokers.add(invoker4);
|
||||
|
|
@ -256,7 +260,7 @@ class AbstractClusterInvokerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSelect_multiInvokers() throws Exception {
|
||||
void testSelect_multiInvokers() {
|
||||
testSelect_multiInvokers(RoundRobinLoadBalance.NAME);
|
||||
testSelect_multiInvokers(LeastActiveLoadBalance.NAME);
|
||||
testSelect_multiInvokers(RandomLoadBalance.NAME);
|
||||
|
|
@ -409,7 +413,7 @@ class AbstractClusterInvokerTest {
|
|||
}
|
||||
|
||||
|
||||
public void testSelect_multiInvokers(String lbname) throws Exception {
|
||||
public void testSelect_multiInvokers(String lbname) {
|
||||
|
||||
int min = 100, max = 500;
|
||||
Double d = (Math.random() * (max - min + 1) + min);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ClusterUtilsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testMergeUrl() throws Exception {
|
||||
void testMergeUrl() {
|
||||
URL providerURL = URL.valueOf("dubbo://localhost:55555");
|
||||
providerURL = providerURL.setPath("path")
|
||||
.setUsername("username")
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.dubbo.rpc.cluster.support;
|
|||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
|
|
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.cluster.Directory;
|
|||
import org.apache.dubbo.rpc.cluster.LoadBalance;
|
||||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -70,6 +72,7 @@ class ConnectivityValidationTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
invoker1 = Mockito.mock(Invoker.class);
|
||||
invoker2 = Mockito.mock(Invoker.class);
|
||||
invoker3 = Mockito.mock(Invoker.class);
|
||||
|
|
|
|||
|
|
@ -16,11 +16,8 @@
|
|||
*/
|
||||
package org.apache.dubbo.rpc.cluster.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.AppResponse;
|
||||
import org.apache.dubbo.rpc.AsyncRpcResult;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
|
|
@ -33,10 +30,15 @@ import org.apache.dubbo.rpc.cluster.Directory;
|
|||
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
|
||||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.cluster.router.state.BitList;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.apache.dubbo.rpc.protocol.AbstractInvoker;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
|
@ -65,7 +67,7 @@ class FailoverClusterInvokerTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
dic = mock(Directory.class);
|
||||
|
||||
given(dic.getUrl()).willReturn(url);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class DefaultProviderURLMergeProcessorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testMergeUrl() throws Exception {
|
||||
void testMergeUrl() {
|
||||
URL providerURL = URL.valueOf("dubbo://localhost:55555");
|
||||
providerURL = providerURL.setPath("path")
|
||||
.setUsername("username")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.support.wrapper;
|
|||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Protocol;
|
||||
|
|
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.RpcInvocation;
|
|||
import org.apache.dubbo.rpc.cluster.LoadBalance;
|
||||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.apache.dubbo.rpc.support.MockProtocol;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
|
@ -49,6 +51,7 @@ class MockClusterInvokerTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeMethod() {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
invokers.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.support.wrapper;
|
|||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.ProxyFactory;
|
||||
|
|
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.cluster.LoadBalance;
|
|||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -46,6 +48,7 @@ class MockProviderRpcExceptionTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeMethod() {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
invokers.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.dubbo.rpc.cluster.support.wrapper;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
|
||||
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
|
||||
import org.apache.dubbo.rpc.Exporter;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Protocol;
|
||||
import org.apache.dubbo.rpc.ProxyFactory;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.RpcInvocation;
|
||||
import org.apache.dubbo.rpc.cluster.LoadBalance;
|
||||
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
|
||||
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.EXPORTER_LISTENER_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
|
||||
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
|
||||
import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.SCOPE_KEY;
|
||||
|
||||
class ScopeClusterInvokerTest {
|
||||
|
||||
private final List<Invoker<DemoService>> invokers = new ArrayList<>();
|
||||
|
||||
private final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
|
||||
private final ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
|
||||
|
||||
private final List<Exporter<?>> exporters = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void beforeMonth() {
|
||||
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() throws Exception {
|
||||
for (Exporter<?> exporter : exporters) {
|
||||
exporter.unexport();
|
||||
}
|
||||
exporters.clear();
|
||||
for (Invoker<DemoService> invoker : invokers) {
|
||||
invoker.destroy();
|
||||
}
|
||||
invokers.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopeNull_RemoteInvoke() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
//Configured with mock
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("doSomething1");
|
||||
Result ret = cluster.invoke(invocation);
|
||||
Assertions.assertEquals("doSomething1", ret.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopeNull_LocalInvoke() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
|
||||
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule()) ;
|
||||
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
|
||||
exporters.add(exporter);
|
||||
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
//Configured with mock
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("doSomething2");
|
||||
invocation.setParameterTypes(new Class[]{});
|
||||
Result ret = cluster.invoke(invocation);
|
||||
Assertions.assertEquals("doSomething2", ret.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopeRemoteInvoke() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.addParameter(SCOPE_KEY, "remote");
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
|
||||
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
|
||||
exporters.add(exporter);
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
//Configured with mock
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("doSomething3");
|
||||
invocation.setParameterTypes(new Class[]{});
|
||||
Result ret = cluster.invoke(invocation);
|
||||
Assertions.assertEquals("doSomething3", ret.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopeLocalInvoke() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.addParameter(SCOPE_KEY, SCOPE_LOCAL);
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
|
||||
.addParameter(INTERFACE_KEY, DemoService.class.getName());
|
||||
injvmUrl = injvmUrl.addParameter(EXPORTER_LISTENER_KEY, LOCAL_PROTOCOL).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
|
||||
exporters.add(exporter);
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("doSomething4");
|
||||
invocation.setParameterTypes(new Class[]{});
|
||||
Assertions.assertTrue(cluster.isAvailable(), "");
|
||||
Result ret = cluster.invoke(invocation);
|
||||
Assertions.assertEquals("doSomething4", ret.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInjvmRefer() {
|
||||
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
|
||||
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
|
||||
exporters.add(exporter);
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
//Configured with mock
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("doSomething4");
|
||||
invocation.setParameterTypes(new Class[]{});
|
||||
Assertions.assertTrue(cluster.isAvailable(), "");
|
||||
Result ret = cluster.invoke(invocation);
|
||||
Assertions.assertEquals("doSomething4", ret.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListenUnExport() throws NoSuchFieldException, IllegalAccessException {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
|
||||
url = url.addParameter(REFER_KEY,
|
||||
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
|
||||
url = url.addParameter(SCOPE_KEY, "local");
|
||||
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
|
||||
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
|
||||
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
|
||||
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
|
||||
|
||||
Invoker<DemoService> cluster = getClusterInvoker(url);
|
||||
invokers.add(cluster);
|
||||
|
||||
exporter.unexport();
|
||||
|
||||
Assertions.assertTrue(cluster instanceof ScopeClusterInvoker);
|
||||
|
||||
Field injvmInvoker = cluster.getClass().getDeclaredField("injvmInvoker");
|
||||
injvmInvoker.setAccessible(true);
|
||||
Assertions.assertNull(injvmInvoker.get(cluster));
|
||||
|
||||
Field isExported = cluster.getClass().getDeclaredField("isExported");
|
||||
isExported.setAccessible(true);
|
||||
Assertions.assertFalse(((AtomicBoolean) isExported.get(cluster)).get());
|
||||
}
|
||||
|
||||
private Invoker<DemoService> getClusterInvoker(URL url) {
|
||||
final URL durl = url.addParameter("proxy", "jdk");
|
||||
invokers.clear();
|
||||
ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension("jdk");
|
||||
Invoker<DemoService> invoker1 = proxy.getInvoker(new DemoServiceImpl(), DemoService.class, durl);
|
||||
invokers.add(invoker1);
|
||||
|
||||
StaticDirectory<DemoService> dic = new StaticDirectory<>(durl, invokers, null);
|
||||
dic.buildRouterChain();
|
||||
AbstractClusterInvoker<DemoService> cluster = new AbstractClusterInvoker(dic) {
|
||||
@Override
|
||||
protected Result doInvoke(Invocation invocation, List invokers, LoadBalance loadbalance)
|
||||
throws RpcException {
|
||||
if (durl.getParameter("invoke_return_error", false)) {
|
||||
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "test rpc exception");
|
||||
} else {
|
||||
return ((Invoker<?>) invokers.get(0)).invoke(invocation);
|
||||
}
|
||||
}
|
||||
};
|
||||
return new ScopeClusterInvoker<>(dic, cluster);
|
||||
}
|
||||
|
||||
public static interface DemoService {
|
||||
String doSomething1();
|
||||
|
||||
String doSomething2();
|
||||
|
||||
String doSomething3();
|
||||
|
||||
String doSomething4();
|
||||
}
|
||||
|
||||
public static class DemoServiceImpl implements DemoService {
|
||||
|
||||
@Override
|
||||
public String doSomething1() {
|
||||
return "doSomething1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doSomething2() {
|
||||
return "doSomething2";
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doSomething3() {
|
||||
return "doSomething3";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doSomething4() {
|
||||
return "doSomething4";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -29,4 +29,4 @@ configs:
|
|||
loadbalance: random
|
||||
cluster: failfast
|
||||
timeout: 6666
|
||||
...
|
||||
...
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
#
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Application scope, apply to all services.
|
||||
---
|
||||
configVersion: v3.0
|
||||
scope: application
|
||||
key: demo-provider
|
||||
configs:
|
||||
- match:
|
||||
address:
|
||||
wildcard: "10.0.0.1:20880"
|
||||
service:
|
||||
oneof:
|
||||
- exact: "DemoService"
|
||||
- exact: "DemoService2"
|
||||
param:
|
||||
- key: match_key1
|
||||
value:
|
||||
exact: value1
|
||||
side: provider
|
||||
parameters:
|
||||
weight: 200
|
||||
...
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Application scope, apply to all services.
|
||||
---
|
||||
configVersion: v3.0
|
||||
scope: application
|
||||
key: demo-provider
|
||||
configs:
|
||||
- addresses:
|
||||
- "10.0.0.1:20880" # compatibility test
|
||||
services:
|
||||
- "DemoService" # compatibility test
|
||||
match:
|
||||
param:
|
||||
- key: match_key1
|
||||
value:
|
||||
exact: value1
|
||||
side: provider
|
||||
parameters:
|
||||
weight: 200
|
||||
...
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Application scope, apply to all services.
|
||||
---
|
||||
configVersion: v3.0
|
||||
scope: application
|
||||
key: demo-provider
|
||||
configs:
|
||||
- addresses:
|
||||
- "10.0.0.1:20880" # duplicate test
|
||||
services: [ "DemoService" ] # duplicate test
|
||||
match:
|
||||
address:
|
||||
wildcard: "10.0.0.1:20880"
|
||||
service:
|
||||
oneof:
|
||||
- exact: "DemoService"
|
||||
- exact: "DemoService2"
|
||||
param:
|
||||
- key: match_key1
|
||||
value:
|
||||
exact: value1
|
||||
side: provider
|
||||
parameters:
|
||||
weight: 200
|
||||
...
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
#
|
||||
#
|
||||
|
||||
# Application scope, no service means apply to all
|
||||
---
|
||||
configVersion: v3.0
|
||||
key: demo-provider
|
||||
type: javascript
|
||||
script: |
|
||||
(function route(invokers) {
|
||||
var result = new java.util.ArrayList(invokers.size());
|
||||
for (i = 0; i < invokers.size(); i ++) {
|
||||
if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
|
||||
result.add(invokers.get(i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} (invokers)); // 表示立即执行方法
|
||||
...
|
||||
|
|
@ -23,8 +23,16 @@
|
|||
<param name="ConversionPattern" value="[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<param name="ConversionPattern" value="[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root>
|
||||
<level value="INFO"/>
|
||||
<appender-ref ref="DUBBO"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</log4j:configuration>
|
||||
</log4j:configuration>
|
||||
|
|
|
|||
|
|
@ -65,12 +65,12 @@
|
|||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
|
@ -100,8 +100,38 @@
|
|||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-version-infos</id>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<failOnError>true</failOnError>
|
||||
<target>
|
||||
<property name="version_file"
|
||||
value="${project.build.outputDirectory}/META-INF/version"/>
|
||||
<!-- get the current version of dubbo -->
|
||||
<echo message="revision=${revision}${line.separator}" file="${version_file}"/>
|
||||
<echo message="git.commit.id=" file="${version_file}" append="true"/>
|
||||
<!-- get the latest commit id -->
|
||||
<exec executable="git" output="${version_file}" error=".git.exec.error" append="true"
|
||||
timeout="3000" failifexecutionfails="false">
|
||||
<arg line="rev-parse HEAD"/>
|
||||
</exec>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
|
|||
import org.apache.dubbo.common.config.ConfigurationCache;
|
||||
import org.apache.dubbo.common.convert.ConverterUtil;
|
||||
import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
|
||||
import org.apache.dubbo.common.ssl.CertManager;
|
||||
import org.apache.dubbo.common.status.reporter.FrameworkStatusReportService;
|
||||
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
|
||||
import org.apache.dubbo.common.utils.DefaultSerializeClassChecker;
|
||||
|
|
@ -38,6 +39,7 @@ public class CommonScopeModelInitializer implements ScopeModelInitializer {
|
|||
beanFactory.registerBean(ConverterUtil.class);
|
||||
beanFactory.registerBean(SerializeSecurityManager.class);
|
||||
beanFactory.registerBean(DefaultSerializeClassChecker.class);
|
||||
beanFactory.registerBean(CertManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import java.util.TreeMap;
|
|||
import java.util.function.Predicate;
|
||||
|
||||
import static org.apache.dubbo.common.BaseServiceMetadata.COLON_SEPARATOR;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ADDRESS_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
|
||||
|
|
@ -115,7 +116,7 @@ class URL implements Serializable {
|
|||
|
||||
private static final long serialVersionUID = -1985165475234910535L;
|
||||
|
||||
private static Map<String, URL> cachedURLs = new LRUCache<>();
|
||||
private static final Map<String, URL> cachedURLs = new LRUCache<>();
|
||||
|
||||
private final URLAddress urlAddress;
|
||||
private final URLParam urlParam;
|
||||
|
|
@ -532,6 +533,10 @@ class URL implements Serializable {
|
|||
return path;
|
||||
}
|
||||
|
||||
public Map<String, String> getOriginalParameters() {
|
||||
return this.getParameters();
|
||||
}
|
||||
|
||||
public Map<String, String> getParameters() {
|
||||
return urlParam.getParameters();
|
||||
}
|
||||
|
|
@ -566,6 +571,10 @@ class URL implements Serializable {
|
|||
return decode(getParameter(key, defaultValue));
|
||||
}
|
||||
|
||||
public String getOriginalParameter(String key) {
|
||||
return getParameter(key);
|
||||
}
|
||||
|
||||
public String getParameter(String key) {
|
||||
return urlParam.getParameter(key);
|
||||
}
|
||||
|
|
@ -1097,8 +1106,17 @@ class URL implements Serializable {
|
|||
return urlParam.getParameter(key);
|
||||
}
|
||||
|
||||
public Map<String, String> toOriginalMap() {
|
||||
Map<String, String> map = new HashMap<>(getOriginalParameters());
|
||||
return addSpecialKeys(map);
|
||||
}
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
Map<String, String> map = new HashMap<>(getParameters());
|
||||
return addSpecialKeys(map);
|
||||
}
|
||||
|
||||
private Map<String, String> addSpecialKeys(Map<String, String> map) {
|
||||
if (getProtocol() != null) {
|
||||
map.put(PROTOCOL_KEY, getProtocol());
|
||||
}
|
||||
|
|
@ -1117,6 +1135,9 @@ class URL implements Serializable {
|
|||
if (getPath() != null) {
|
||||
map.put(PATH_KEY, getPath());
|
||||
}
|
||||
if (getAddress() != null) {
|
||||
map.put(ADDRESS_KEY, getAddress());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -1596,10 +1617,18 @@ class URL implements Serializable {
|
|||
}
|
||||
|
||||
/* add service scope operations, see InstanceAddressURL */
|
||||
public Map<String, String> getOriginalServiceParameters(String service) {
|
||||
return getServiceParameters(service);
|
||||
}
|
||||
|
||||
public Map<String, String> getServiceParameters(String service) {
|
||||
return getParameters();
|
||||
}
|
||||
|
||||
public String getOriginalServiceParameter(String service, String key) {
|
||||
return this.getServiceParameter(service, key);
|
||||
}
|
||||
|
||||
public String getServiceParameter(String service, String key) {
|
||||
return getParameter(key);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ public final class URLBuilder extends ServiceConfigURL {
|
|||
|
||||
private String path;
|
||||
|
||||
private Map<String, String> parameters;
|
||||
private final Map<String, String> parameters;
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
private Map<String, Map<String, String>> methodParameters;
|
||||
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ public final class URLStrParser {
|
|||
String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
|
||||
String value;
|
||||
if (valueStart >= valueEnd) {
|
||||
value = name;
|
||||
value = "";
|
||||
} else {
|
||||
value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
|
||||
}
|
||||
|
|
@ -291,7 +291,7 @@ public final class URLStrParser {
|
|||
String name = str.substring(nameStart, valueStart - 1);
|
||||
String value;
|
||||
if (valueStart >= valueEnd) {
|
||||
value = name;
|
||||
value = "";
|
||||
} else {
|
||||
value = str.substring(valueStart, valueEnd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,19 @@ package org.apache.dubbo.common;
|
|||
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.ClassUtils;
|
||||
import org.apache.dubbo.common.utils.ConfigUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -46,8 +50,9 @@ public final class Version {
|
|||
public static final String DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2";
|
||||
// version 1.0.0 represents Dubbo rpc protocol before v2.6.2
|
||||
public static final int LEGACY_DUBBO_PROTOCOL_VERSION = 10000; // 1.0.0
|
||||
// Dubbo implementation version, usually is jar version.
|
||||
private static final String VERSION = getVersion(Version.class, "");
|
||||
// Dubbo implementation version.
|
||||
private static String VERSION;
|
||||
private static String LATEST_COMMIT_ID;
|
||||
|
||||
/**
|
||||
* For protocol compatibility purpose.
|
||||
|
|
@ -61,6 +66,21 @@ public final class Version {
|
|||
static {
|
||||
// check if there's duplicated jar
|
||||
Version.checkDuplicate(Version.class);
|
||||
|
||||
// get dubbo version and last commit id
|
||||
try {
|
||||
Properties properties =
|
||||
ConfigUtils.loadProperties(Collections.emptySet(), "META-INF/version");
|
||||
|
||||
VERSION = Optional.ofNullable(properties.getProperty("revision"))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElseGet(() -> getVersion(Version.class, ""));
|
||||
LATEST_COMMIT_ID = Optional.ofNullable(properties.getProperty("git.commit.id")).orElse("");
|
||||
} catch (Throwable e) {
|
||||
logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", "continue the old logic, ignore exception " + e.getMessage(), e);
|
||||
VERSION = getVersion(Version.class, "");
|
||||
LATEST_COMMIT_ID = "";
|
||||
}
|
||||
}
|
||||
|
||||
private Version() {
|
||||
|
|
@ -74,6 +94,10 @@ public final class Version {
|
|||
return VERSION;
|
||||
}
|
||||
|
||||
public static String getLastCommitId() {
|
||||
return LATEST_COMMIT_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare versions
|
||||
*
|
||||
|
|
|
|||
|
|
@ -47,12 +47,13 @@ public class ScopeBeanFactory {
|
|||
protected static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(ScopeBeanFactory.class);
|
||||
|
||||
private final ScopeBeanFactory parent;
|
||||
private ExtensionAccessor extensionAccessor;
|
||||
private List<ExtensionPostProcessor> extensionPostProcessors;
|
||||
private ConcurrentHashMap<Class, AtomicInteger> beanNameIdCounterMap = new ConcurrentHashMap<>();
|
||||
private List<BeanInfo> registeredBeanInfos = new CopyOnWriteArrayList<>();
|
||||
private final ExtensionAccessor extensionAccessor;
|
||||
private final List<ExtensionPostProcessor> extensionPostProcessors;
|
||||
private final ConcurrentHashMap<Class<?>, AtomicInteger> beanNameIdCounterMap = new ConcurrentHashMap<>();
|
||||
private final List<BeanInfo> registeredBeanInfos = new CopyOnWriteArrayList<>();
|
||||
private InstantiationStrategy instantiationStrategy;
|
||||
private AtomicBoolean destroyed = new AtomicBoolean();
|
||||
private final AtomicBoolean destroyed = new AtomicBoolean();
|
||||
private List<Class<?>> registeredClasses = new ArrayList<>();
|
||||
|
||||
public ScopeBeanFactory(ScopeBeanFactory parent, ExtensionAccessor extensionAccessor) {
|
||||
this.parent = parent;
|
||||
|
|
@ -131,6 +132,7 @@ public class ScopeBeanFactory {
|
|||
}
|
||||
}
|
||||
}
|
||||
registeredClasses.add(type);
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +209,7 @@ public class ScopeBeanFactory {
|
|||
return bean;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getBeanInternal(String name, Class<T> type) {
|
||||
checkDestroyed();
|
||||
// All classes are derived from java.lang.Object, cannot filter bean by it
|
||||
|
|
@ -272,12 +275,16 @@ public class ScopeBeanFactory {
|
|||
}
|
||||
|
||||
static class BeanInfo {
|
||||
private String name;
|
||||
private Object instance;
|
||||
private final String name;
|
||||
private final Object instance;
|
||||
|
||||
public BeanInfo(String name, Object instance) {
|
||||
this.name = name;
|
||||
this.instance = instance;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Class<?>> getRegisteredClasses() {
|
||||
return registeredClasses;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public final class JavaBeanDescriptor implements Serializable, Iterable<Map.Entr
|
|||
private String className;
|
||||
private int type;
|
||||
|
||||
private Map<Object, Object> properties = new LinkedHashMap<>();
|
||||
private final Map<Object, Object> properties = new LinkedHashMap<>();
|
||||
|
||||
public JavaBeanDescriptor() {}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue