Merge pull request #366 from guohao/add-dubbo-samples-migration

[ISSUE-365] Add dubbo-samples-migration
diff --git a/README.md b/README.md
index cffaa7b..0cf3604 100644
--- a/README.md
+++ b/README.md
@@ -179,6 +179,7 @@
  * [dubbo-samples-annotation](dubbo-samples-annotation/case-configuration.yml) : A simple provider service with builtin zookeeper.
  * [dubbo-samples-api](dubbo-samples-api/case-configuration.yml) : A simple provider service with external zookeeper.
  * [dubbo-samples-chain](dubbo-samples-chain/case-configuration.yml) : A multiple services with external zookeeper.
+ * [dubbo-samples-migration](dubbo-samples-migration/case-configuration.yml) : A compatibility test with the provider and consumer have different dubbo verison. 
 
 
 That's it, then feel free to add more integration test for the Dubbo project, have fun.
diff --git a/dubbo-samples-migration/README.md b/dubbo-samples-migration/README.md
new file mode 100644
index 0000000..ed95960
--- /dev/null
+++ b/dubbo-samples-migration/README.md
@@ -0,0 +1,34 @@
+## Compatibility Testing Scenario Usage
+Pre-requirements: Both provider and consumer can not use the same `POM`, because you cannot guarantee that all the codes you write Dubbo 2.0 and 3.0 support at the same time,  they must be divided into two independent projects. This is also applicable in daily business compatibility testing
+
+Add new grammar to `case-versions.conf`:
+```yaml
+# 原常规配置
+# dubbo.version=2.7*, 3.*
+spring.version=4.*, 5.*
+
+# 支持不同的 servcie 应用,配置不同的 dubbo 版本依赖,与 dubbo.version 二选一
+# 为防止构建用例倍级增长,不建议配置多个版本
+# dubbo.{service}.verison 中的 service 可以任意自定义
+dubbo.provider.version=3.*
+dubbo.consumer.version=2.7.*
+```
+And then in each consumer and provider project, you can configure `POM`'s properties as follows:
+```xml
+#  consumer side
+<properties>
+    <dubbo.consumer.version>2.7.13</dubbo.consumer.version>
+</properties>
+
+# provider side
+<properties>
+    <dubbo.provider.version>2.7.13</dubbo.provider.version>
+</properties>
+```
+
+When the Integration Tests trigger, these properties will be overwritten by maven, for example:
+```shell
+mvn -Ddubbo.provider.version=3.0.3-SNAPSHOT -Ddubbo.consumer.version=2.7.13 -Dspring.version=4.3.16.RELEASE
+```
+
+
diff --git a/dubbo-samples-migration/dubbo-samples-migration-api/pom.xml b/dubbo-samples-migration/dubbo-samples-migration-api/pom.xml
new file mode 100644
index 0000000..c2100f2
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-api/pom.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+    
+    <groupId>org.apache.dubbo</groupId>
+    <artifactId>dubbo-samples-migration-api</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+</project>
\ No newline at end of file
diff --git a/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/GreeterService.java b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/GreeterService.java
new file mode 100644
index 0000000..9a14997
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/GreeterService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.migration.api;
+
+public interface GreeterService {
+
+    /**
+     * Sends a greeting
+     */
+    HelloReply sayHello(HelloRequest request);
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloReply.java b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloReply.java
new file mode 100644
index 0000000..ec88fde
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloReply.java
@@ -0,0 +1,18 @@
+package org.apache.dubbo.migration.api;
+
+import java.io.Serializable;
+
+/**
+ * HelloReply
+ */
+public class HelloReply implements Serializable {
+    private String message;
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloRequest.java b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloRequest.java
new file mode 100644
index 0000000..93d1870
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-api/src/main/java/org/apache/dubbo/migration/api/HelloRequest.java
@@ -0,0 +1,18 @@
+package org.apache.dubbo.migration.api;
+
+import java.io.Serializable;
+
+/**
+ * HelloRequest
+ */
+public class HelloRequest implements Serializable {
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-case-default/case-configuration.yml b/dubbo-samples-migration/dubbo-samples-migration-case-default/case-configuration.yml
new file mode 100644
index 0000000..c87fe01
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-case-default/case-configuration.yml
@@ -0,0 +1,50 @@
+# 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.
+
+props:
+  app_provider: dubbo-samples-migration-provider
+  app_provider_main_class: org.apache.dubbo.migration.provider.ApiProvider
+  app_consumer: dubbo-samples-migration-consumer
+  zookeeper_port: 2181
+  dubbo_port: 20880
+  
+services:
+  ${app_provider}:
+    type: app
+    basedir: ../${app_provider}
+    mainClass: ${app_provider_main_class}
+    systemProps:
+      - dubbo.current.protocol=dubbo
+      - zookeeper.address=${app_provider}
+      - dubbo.application.register-mode=all
+    checkPorts:
+      - ${zookeeper_port}
+      - ${dubbo_port}
+    checkLog: "dubbo service started"
+    
+  ${app_consumer}-test:
+    type: test
+    basedir: ../${app_consumer} 
+    tests:
+      - "**/*IT.class"
+    systemProps:
+      - dubbo.current.protocol=dubbo
+      - zookeeper.address=${app_provider}
+    waitPortsBeforeRun:
+      - ${app_provider}:${zookeeper_port}
+      - ${app_provider}:${dubbo_port}
+    depends_on:
+      - ${app_provider}
\ No newline at end of file
diff --git a/dubbo-samples-migration/dubbo-samples-migration-case-default/case-versions.conf b/dubbo-samples-migration/dubbo-samples-migration-case-default/case-versions.conf
new file mode 100644
index 0000000..3e63442
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-case-default/case-versions.conf
@@ -0,0 +1,26 @@
+#
+#
+#   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.
+#
+
+# Supported component versions of the test case
+
+# Spring app
+spring.version=4.*, 5.*
+
+# Support Compatibility test
+dubbo.provider.version=3.*
+dubbo.consumer.version=2.7.*
diff --git a/dubbo-samples-migration/dubbo-samples-migration-case-default/pom.xml b/dubbo-samples-migration/dubbo-samples-migration-case-default/pom.xml
new file mode 100644
index 0000000..6c9a501
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-case-default/pom.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-samples-migration-case-default</artifactId>
+    <groupId>org.apache.dubbo</groupId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    
+    <modules>
+        <module>../dubbo-samples-migration-api</module>
+        <module>../dubbo-samples-migration-provider</module>
+        <module>../dubbo-samples-migration-consumer</module>
+    </modules>
+</project>
\ No newline at end of file
diff --git a/dubbo-samples-migration/dubbo-samples-migration-consumer/pom.xml b/dubbo-samples-migration/dubbo-samples-migration-consumer/pom.xml
new file mode 100644
index 0000000..c2cd67b
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-consumer/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.example</groupId>
+    <artifactId>dubbo-samples-migration-consumer</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <dubbo.consumer.version>2.7.13</dubbo.consumer.version>
+        <junit.version>4.12</junit.version>
+        <maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
+        <source.level>1.8</source.level>
+        <target.level>1.8</target.level>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-bom</artifactId>
+                <version>${dubbo.consumer.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-dependencies-zookeeper</artifactId>
+                <version>${dubbo.consumer.version}</version>
+                <type>pom</type>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-dependencies-bom</artifactId>
+                <version>${dubbo.consumer.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-dependencies-zookeeper</artifactId>
+            <type>pom</type>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-samples-migration-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <!-- For jdk 11 above JavaEE annotation -->
+        <profile>
+            <id>javax.annotation</id>
+            <activation>
+                <jdk>[1.11,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>javax.annotation</groupId>
+                    <artifactId>javax.annotation-api</artifactId>
+                    <version>1.3.2</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/dubbo-samples-migration/dubbo-samples-migration-consumer/src/main/resources/log4j.properties b/dubbo-samples-migration/dubbo-samples-migration-consumer/src/main/resources/log4j.properties
new file mode 100644
index 0000000..3fa7599
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-consumer/src/main/resources/log4j.properties
@@ -0,0 +1,26 @@
+#
+#
+#   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.
+#
+#
+
+###set log levels###
+log4j.rootLogger=debug, stdout
+###output to the console###
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%n
diff --git a/dubbo-samples-migration/dubbo-samples-migration-consumer/src/test/java/org/apache/dubbo/migration/consumer/ApiConsumerIT.java b/dubbo-samples-migration/dubbo-samples-migration-consumer/src/test/java/org/apache/dubbo/migration/consumer/ApiConsumerIT.java
new file mode 100644
index 0000000..7c4ffaf
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-consumer/src/test/java/org/apache/dubbo/migration/consumer/ApiConsumerIT.java
@@ -0,0 +1,44 @@
+package org.apache.dubbo.migration.consumer;
+
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+import org.apache.dubbo.migration.api.GreeterService;
+import org.apache.dubbo.migration.api.HelloReply;
+import org.apache.dubbo.migration.api.HelloRequest;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Consumer test side
+ */
+public class ApiConsumerIT {
+    @Test
+    public void consumeInvoke() {
+        String curProtocol = System.getProperty("dubbo.current.protocol", CommonConstants.DUBBO);
+        String zookeeperAddress = System.getProperty("zookeeper.address", "127.0.0.1");
+        
+        ReferenceConfig<GreeterService> referenceConfig = new ReferenceConfig<>();
+        referenceConfig.setInterface(GreeterService.class);
+        referenceConfig.setCheck(false);
+        referenceConfig.setProtocol(curProtocol);
+        referenceConfig.setLazy(true);
+        referenceConfig.setTimeout(100000);
+
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(new ApplicationConfig("dubbo-samples-migration-consumer"))
+                .registry(new RegistryConfig("zookeeper://" + zookeeperAddress + ":2181"))
+                .reference(referenceConfig)
+                .start();
+
+        GreeterService greeterService = referenceConfig.get();
+        HelloRequest helloRequest = new HelloRequest();
+        helloRequest.setName(curProtocol);
+        final HelloReply reply = greeterService.sayHello(helloRequest);
+        
+        Assert.assertEquals("Hello " + curProtocol, reply.getMessage());
+    }
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-provider/pom.xml b/dubbo-samples-migration/dubbo-samples-migration-provider/pom.xml
new file mode 100644
index 0000000..5d4d889
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-provider/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.dubbo</groupId>
+    <artifactId>dubbo-samples-migration-provider</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <dubbo.provider.version>3.0.2.1</dubbo.provider.version>
+        <junit.version>4.12</junit.version>
+        <maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
+        <source.level>1.8</source.level>
+        <target.level>1.8</target.level>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-bom</artifactId>
+                <version>${dubbo.provider.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-dependencies-zookeeper</artifactId>
+                <version>${dubbo.provider.version}</version>
+                <type>pom</type>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-dependencies-bom</artifactId>
+                <version>${dubbo.provider.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-dependencies-zookeeper</artifactId>
+            <type>pom</type>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-samples-migration-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <!-- For jdk 11 above JavaEE annotation -->
+        <profile>
+            <id>javax.annotation</id>
+            <activation>
+                <jdk>[1.11,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>javax.annotation</groupId>
+                    <artifactId>javax.annotation-api</artifactId>
+                    <version>1.3.2</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/EmbeddedZooKeeper.java b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/EmbeddedZooKeeper.java
new file mode 100644
index 0000000..4f88212
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/EmbeddedZooKeeper.java
@@ -0,0 +1,251 @@
+/*
+ * 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.migration;
+
+import org.apache.zookeeper.server.ServerConfig;
+import org.apache.zookeeper.server.ZooKeeperServerMain;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.SmartLifecycle;
+import org.springframework.util.ErrorHandler;
+import org.springframework.util.SocketUtils;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Properties;
+import java.util.UUID;
+
+/**
+ * from: https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java
+ * <p>
+ * Helper class to start an embedded instance of standalone (non clustered) ZooKeeper.
+ * <p>
+ * NOTE: at least an external standalone server (if not an ensemble) are recommended, even for
+ * {@link org.springframework.xd.dirt.server.singlenode.SingleNodeApplication}
+ *
+ * @author Patrick Peralta
+ * @author Mark Fisher
+ * @author David Turanski
+ */
+public class EmbeddedZooKeeper implements SmartLifecycle {
+
+    /**
+     * Logger.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class);
+
+    /**
+     * ZooKeeper client port. This will be determined dynamically upon startup.
+     */
+    private final int clientPort;
+
+    /**
+     * Whether to auto-start. Default is true.
+     */
+    private boolean autoStartup = true;
+
+    /**
+     * Lifecycle phase. Default is 0.
+     */
+    private int phase = 0;
+
+    /**
+     * Thread for running the ZooKeeper server.
+     */
+    private volatile Thread zkServerThread;
+
+    /**
+     * ZooKeeper server.
+     */
+    private volatile ZooKeeperServerMain zkServer;
+
+    /**
+     * {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread.
+     */
+    private ErrorHandler errorHandler;
+
+    private boolean daemon = true;
+
+    /**
+     * Construct an EmbeddedZooKeeper with a random port.
+     */
+    public EmbeddedZooKeeper() {
+        clientPort = SocketUtils.findAvailableTcpPort();
+    }
+
+    /**
+     * Construct an EmbeddedZooKeeper with the provided port.
+     *
+     * @param clientPort port for ZooKeeper server to bind to
+     */
+    public EmbeddedZooKeeper(int clientPort, boolean daemon) {
+        this.clientPort = clientPort;
+        this.daemon = daemon;
+    }
+
+    /**
+     * Returns the port that clients should use to connect to this embedded server.
+     *
+     * @return dynamically determined client port
+     */
+    public int getClientPort() {
+        return this.clientPort;
+    }
+
+    /**
+     * Specify whether to start automatically. Default is true.
+     *
+     * @param autoStartup whether to start automatically
+     */
+    public void setAutoStartup(boolean autoStartup) {
+        this.autoStartup = autoStartup;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAutoStartup() {
+        return this.autoStartup;
+    }
+
+    /**
+     * Specify the lifecycle phase for the embedded server.
+     *
+     * @param phase the lifecycle phase
+     */
+    public void setPhase(int phase) {
+        this.phase = phase;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getPhase() {
+        return this.phase;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isRunning() {
+        return (zkServerThread != null);
+    }
+
+    /**
+     * Start the ZooKeeper server in a background thread.
+     * <p>
+     * Register an error handler via {@link #setErrorHandler} in order to handle
+     * any exceptions thrown during startup or execution.
+     */
+    @Override
+    public synchronized void start() {
+        if (zkServerThread == null) {
+            zkServerThread = new Thread(new ServerRunnable(), "ZooKeeper Server Starter");
+            zkServerThread.setDaemon(daemon);
+            zkServerThread.start();
+        }
+    }
+
+    /**
+     * Shutdown the ZooKeeper server.
+     */
+    @Override
+    public synchronized void stop() {
+        if (zkServerThread != null) {
+            // The shutdown method is protected...thus this hack to invoke it.
+            // This will log an exception on shutdown; see
+            // https://issues.apache.org/jira/browse/ZOOKEEPER-1873 for details.
+            try {
+                Method shutdown = ZooKeeperServerMain.class.getDeclaredMethod("shutdown");
+                shutdown.setAccessible(true);
+                shutdown.invoke(zkServer);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+
+            // It is expected that the thread will exit after
+            // the server is shutdown; this will block until
+            // the shutdown is complete.
+            try {
+                zkServerThread.join(5000);
+                zkServerThread = null;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                logger.warn("Interrupted while waiting for embedded ZooKeeper to exit");
+                // abandoning zk thread
+                zkServerThread = null;
+            }
+        }
+    }
+
+    /**
+     * Stop the server if running and invoke the callback when complete.
+     */
+    @Override
+    public void stop(Runnable callback) {
+        stop();
+        callback.run();
+    }
+
+    /**
+     * Provide an {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. If none
+     * is provided, only error-level logging will occur.
+     *
+     * @param errorHandler the {@link ErrorHandler} to be invoked
+     */
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
+    /**
+     * Runnable implementation that starts the ZooKeeper server.
+     */
+    private class ServerRunnable implements Runnable {
+
+        @Override
+        public void run() {
+            try {
+                Properties properties = new Properties();
+                File file = new File(System.getProperty("java.io.tmpdir")
+                        + File.separator + UUID.randomUUID());
+                file.deleteOnExit();
+                properties.setProperty("dataDir", file.getAbsolutePath());
+                properties.setProperty("clientPort", String.valueOf(clientPort));
+
+                QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig();
+                quorumPeerConfig.parseProperties(properties);
+
+                zkServer = new ZooKeeperServerMain();
+                ServerConfig configuration = new ServerConfig();
+                configuration.readFrom(quorumPeerConfig);
+
+                zkServer.runFromConfig(configuration);
+            } catch (Exception e) {
+                if (errorHandler != null) {
+                    errorHandler.handleError(e);
+                } else {
+                    logger.error("Exception running embedded ZooKeeper", e);
+                }
+            }
+        }
+    }
+
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/GreeterServiceImpl.java b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/GreeterServiceImpl.java
new file mode 100644
index 0000000..918da85
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/GreeterServiceImpl.java
@@ -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.migration;
+
+
+import org.apache.dubbo.migration.api.GreeterService;
+import org.apache.dubbo.migration.api.HelloReply;
+import org.apache.dubbo.migration.api.HelloRequest;
+
+public class GreeterServiceImpl implements GreeterService {
+
+    @Override
+    public HelloReply sayHello(HelloRequest request) {
+        HelloReply helloReply = new HelloReply();
+        helloReply.setMessage("Hello " + request.getName());
+        return helloReply;
+    }
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/provider/ApiProvider.java b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/provider/ApiProvider.java
new file mode 100644
index 0000000..8312b5c
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/java/org/apache/dubbo/migration/provider/ApiProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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.migration.provider;
+
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+import org.apache.dubbo.migration.EmbeddedZooKeeper;
+import org.apache.dubbo.migration.GreeterServiceImpl;
+import org.apache.dubbo.migration.api.GreeterService;
+
+public class ApiProvider {
+    public static void main(String[] args) throws InterruptedException {
+        String curProtocol = System.getProperty("dubbo.current.protocol", CommonConstants.DUBBO);
+        String zookeeperAddress = System.getProperty("zookeeper.address", "127.0.0.1");
+        
+        new EmbeddedZooKeeper(2181, false).start();
+
+        ServiceConfig<GreeterService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setInterface(GreeterService.class);
+        serviceConfig.setRef(new GreeterServiceImpl());
+
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(new ApplicationConfig("dubbo-samples-migration-provider"))
+                .registry(new RegistryConfig("zookeeper://" + zookeeperAddress + ":2181"))
+                .protocol(new ProtocolConfig(curProtocol))
+                .service(serviceConfig)
+                .start();
+        
+        System.out.println("dubbo service started.");
+        bootstrap.await();
+    }
+}
diff --git a/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/resources/log4j.properties b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/resources/log4j.properties
new file mode 100644
index 0000000..3fa7599
--- /dev/null
+++ b/dubbo-samples-migration/dubbo-samples-migration-provider/src/main/resources/log4j.properties
@@ -0,0 +1,26 @@
+#
+#
+#   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.
+#
+#
+
+###set log levels###
+log4j.rootLogger=debug, stdout
+###output to the console###
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%n
diff --git a/dubbo-samples-migration/pom.xml b/dubbo-samples-migration/pom.xml
new file mode 100644
index 0000000..543e835
--- /dev/null
+++ b/dubbo-samples-migration/pom.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.dubbo</groupId>
+    <artifactId>dubbo-samples-migration</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0-SNAPSHOT</version>
+    
+    <modules>
+        <module>dubbo-samples-migration-api</module>
+        <module>dubbo-samples-migration-provider</module>
+        <module>dubbo-samples-migration-consumer</module>
+    </modules>
+</project>
\ No newline at end of file
diff --git a/dubbo-samples-triple/case-configuration.yml b/dubbo-samples-triple/case-configuration.yml
index 6f8ff89..fa76413 100644
--- a/dubbo-samples-triple/case-configuration.yml
+++ b/dubbo-samples-triple/case-configuration.yml
@@ -18,6 +18,10 @@
   dubbo-samples-triple:
     type: app
     basedir: .
+    checkPorts:
+      - 2181
+      - 50052
+      - 50053
     mainClass: org.apache.dubbo.sample.tri.TriGrpcProvider
 
   dubbo-samples-triple-test:
@@ -42,6 +46,7 @@
     waitPortsBeforeRun:
       - dubbo-samples-triple:2181
       - dubbo-samples-triple:50052
+      - dubbo-samples-triple:50053
     depends_on:
       - dubbo-samples-triple
 
diff --git a/pom.xml b/pom.xml
index 805573f..3833a36 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
         <module>dubbo-samples-local</module>
         <module>dubbo-samples-merge</module>
         <module>dubbo-samples-metadata-report</module>
+        <module>dubbo-samples-migration</module>
         <module>dubbo-samples-mock</module>
         <module>dubbo-samples-monitor</module>
         <module>dubbo-samples-multi-registry</module>
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/Constants.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/Constants.java
index 55fe1f4..c80e7ff 100644
--- a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/Constants.java
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/Constants.java
@@ -17,12 +17,26 @@
 
 package org.apache.dubbo.scenario.builder;
 
-public class Constants {
+import java.util.regex.Pattern;
 
-    public static final String ERROR_MSG_FLAG=":ErrorMsg:";
+/**
+ * Constants in dubbo-scenario-builder
+ */
+public interface Constants {
 
-    public static final int EXIT_FAILED = 1;
-    public static final int EXIT_UNMATCHED = 100;
-    public static final int EXIT_IGNORED = 120;
+    String ERROR_MSG_FLAG=":ErrorMsg:";
 
+    int EXIT_FAILED = 1;
+    int EXIT_UNMATCHED = 100;
+    int EXIT_IGNORED = 120;
+
+    /**
+     * Used in case-versions.conf 
+     */
+    String DUBBO_VERSION_KEY = "dubbo.version";
+
+    /**
+     * The pattern used for searching target service name 
+     */
+    Pattern PATTERN_DUBBO_VERSION = Pattern.compile("^dubbo\\.(.+)\\.version$");
 }
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/VersionMatcher.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/VersionMatcher.java
index 62d0684..b155298 100644
--- a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/VersionMatcher.java
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/VersionMatcher.java
@@ -29,11 +29,13 @@
 import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Multi-version matcher
@@ -41,6 +43,10 @@
 public class VersionMatcher {
 
     private static final Logger logger = LoggerFactory.getLogger(VersionMatcher.class);
+
+    /**
+     * Java Property names 
+     */
     public static final String CASE_VERSIONS_FILE = "caseVersionsFile";
     public static final String CANDIDATE_VERSIONS = "candidateVersions";
     public static final String OUTPUT_FILE = "outputFile";
@@ -85,36 +91,59 @@
         // parse case version match rules
         Map<String, List<MatchRule>> caseVersionMatchRules = parseCaseVersionMatchRules(caseVersionsFile);
 
+        // Filter caseVersionMatchRules
+        chooseActiveDubboVersionRule(caseVersionMatchRules);
+        
         Map<String, List<String>> matchedVersionMap = new LinkedHashMap<>();
 
         candidateVersionMap.forEach((component, candidateVersionList) -> {
-            List<MatchRule> matchRules = caseVersionMatchRules.get(component);
-            if (matchRules == null || matchRules.isEmpty()) {
+            Map<String, List<MatchRule>> matchRulesMap;
+            if (Constants.DUBBO_VERSION_KEY.equals(component) && 
+                !caseVersionMatchRules.containsKey(Constants.DUBBO_VERSION_KEY)
+            ) {
+                matchRulesMap = caseVersionMatchRules.keySet().stream()
+                    .filter(key -> !extractDubboServiceName(key).isEmpty())
+                    .collect(Collectors.toMap(
+                            key -> key,
+                            key -> caseVersionMatchRules.get(key)
+                    ));
+            } else {
+                matchRulesMap = new HashMap<>();
+                if (caseVersionMatchRules.get(component) != null) {
+                    matchRulesMap.put(component, caseVersionMatchRules.get(component));
+                }
+            }
+            
+            if (matchRulesMap.isEmpty()) {
                 return;
             }
 
-            // matching rules
-            List<String> matchedVersionList = new ArrayList<>();
-            for (String version : candidateVersionList) {
-                if (hasIncludeVersion(matchRules, version)) {
-                    matchedVersionList.add(version);
-                }
-            }
-
-            //add case specific version
-            if (matchedVersionList.isEmpty() && includeCaseSpecificVersion) {
-                for (MatchRule matchRule : matchRules) {
-                    if (!matchRule.isExcluded() && matchRule instanceof PlainMatchRule) {
-                        matchedVersionList.add(((PlainMatchRule) matchRule).getVersion());
+            for (String matchRulesKey : matchRulesMap.keySet()) {
+                List<MatchRule> matchRules = matchRulesMap.get(matchRulesKey);
+                
+                // matching rules
+                List<String> matchedVersionList = new ArrayList<>();
+                for (String version : candidateVersionList) {
+                    if (hasIncludeVersion(matchRules, version)) {
+                        matchedVersionList.add(version);
                     }
                 }
-            }
-            if (matchedVersionList.size() > 0) {
-                matchedVersionMap.put(component, matchedVersionList);
+
+                //add case specific version
+                if (matchedVersionList.isEmpty() && includeCaseSpecificVersion) {
+                    for (MatchRule matchRule : matchRules) {
+                        if (!matchRule.isExcluded() && matchRule instanceof PlainMatchRule) {
+                            matchedVersionList.add(((PlainMatchRule) matchRule).getVersion());
+                        }
+                    }
+                }
+                if (matchedVersionList.size() > 0) {
+                    matchedVersionMap.put(matchRulesKey, matchedVersionList);
+                }
             }
         });
 
-        // check if all component has matched version
+        // Check whether all components have matched version
         if (caseVersionMatchRules.size() != matchedVersionMap.size()) {
             List<String> components = new ArrayList<>(caseVersionMatchRules.keySet());
             components.removeAll(matchedVersionMap.keySet());
@@ -152,6 +181,35 @@
         }
     }
 
+    /**
+     * Choose only one final active Dubbo version rule from following case: 
+     * <br>
+     * 1. dubbo.version - original usage
+     * <br>
+     * 2. dubbo.{service}.version - different services can have different dubbo version for supporting compatibility-test
+     * @param caseVersionMatchRules
+     */
+    private static void chooseActiveDubboVersionRule(Map<String, List<MatchRule>> caseVersionMatchRules) {
+        for (String key : caseVersionMatchRules.keySet()) {
+            if (!extractDubboServiceName(key).isEmpty() && caseVersionMatchRules.get(Constants.DUBBO_VERSION_KEY) != null) {
+                errorAndExit(Constants.EXIT_FAILED, "The config item dubbo.version and dubbo.{service}.version can't appear simultaneously");
+            }
+        }
+    }
+
+    /**
+     * Extract service name from the given key
+     * @return the key or empty string when not matched.
+     */
+    public static String extractDubboServiceName(String key) {
+        Matcher matcher = Constants.PATTERN_DUBBO_VERSION.matcher(key);
+        if (matcher.matches()) {
+            return matcher.group(1);
+        }
+        
+        return "";
+    }
+    
     private static boolean hasIncludeVersion(List<MatchRule> matchRules, String version) {
         boolean included = false;
         version = trimVersion(version);
@@ -219,6 +277,7 @@
                 }
                 int p = line.indexOf('=');
                 String component = line.substring(0, p);
+                String serviceName = extractDubboServiceName(component);
                 String patternStr = line.substring(p + 1);
                 patternStr = trimRule(patternStr, "[", "]");
                 String[] patterns = patternStr.split(",");
@@ -235,9 +294,9 @@
                             pattern = pattern.substring(1).trim();
                         }
                         if (pattern.contains("*")) {
-                            ruleList.add(new WildcardMatchRule(excluded, pattern));
+                            ruleList.add(new WildcardMatchRule(excluded, pattern, serviceName));
                         } else {
-                            ruleList.add(new PlainMatchRule(excluded, pattern));
+                            ruleList.add(new PlainMatchRule(excluded, pattern, serviceName));
                         }
                     }
                 }
@@ -298,32 +357,46 @@
     }
 
     private interface MatchRule {
-
         boolean isExcluded();
 
         boolean match(String version);
 
+
+        /**
+         * Get service the MatchRule bind to
+         * @return
+         */
+        default String getServiceName() {
+            return null;
+        }
     }
 
     private static abstract class ExcludableMatchRule implements MatchRule {
         boolean excluded;
+        protected String serviceName;
 
         public ExcludableMatchRule(boolean excluded) {
             this.excluded = excluded;
         }
 
+        @Override
         public boolean isExcluded() {
             return excluded;
         }
 
+        @Override
+        public String getServiceName() {
+            return serviceName;
+        }
     }
 
     private static class PlainMatchRule extends ExcludableMatchRule {
         private String version;
 
-        public PlainMatchRule(boolean excluded, String version) {
+        public PlainMatchRule(boolean excluded, String version, String serviceName) {
             super(excluded);
             this.version = version;
+            this.serviceName = serviceName;
         }
 
         @Override
@@ -345,10 +418,11 @@
         private Pattern versionPattern;
         private String versionWildcard;
 
-        public WildcardMatchRule(boolean excluded, String versionWildcard) {
+        public WildcardMatchRule(boolean excluded, String versionWildcard, String serviceName) {
             super(excluded);
             this.versionPattern = getWildcardPattern(versionWildcard);
             this.versionWildcard = versionWildcard;
+            this.serviceName = serviceName;
         }
 
         @Override
diff --git a/test/run-tests.sh b/test/run-tests.sh
index 60385b3..b150f7c 100755
--- a/test/run-tests.sh
+++ b/test/run-tests.sh
@@ -112,9 +112,6 @@
   case_dir=$1
   case_no=$2
 
-  echo "case_dir=$case_dir"
-  echo "case_no=$case_no"
-
   if [ -f $case_dir ]; then
     case_dir=`dirname $case_dir`
   fi