Support healthcheck and depends_on condition (#221)

diff --git a/dubbo-samples-async/dubbo-samples-async-onerror/src/main/java/org/apache/dubbo/samples/governance/AsyncProvider.java b/dubbo-samples-async/dubbo-samples-async-onerror/src/main/java/org/apache/dubbo/samples/governance/AsyncProvider.java
index 8641afd..803f51a 100644
--- a/dubbo-samples-async/dubbo-samples-async-onerror/src/main/java/org/apache/dubbo/samples/governance/AsyncProvider.java
+++ b/dubbo-samples-async/dubbo-samples-async-onerror/src/main/java/org/apache/dubbo/samples/governance/AsyncProvider.java
@@ -21,13 +21,17 @@
 
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
+import java.util.concurrent.CountDownLatch;
+
 public class AsyncProvider {
 
     public static void main(String[] args) throws Exception {
         new EmbeddedZooKeeper(2181, false).start();
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/async-provider.xml"});
         context.start();
-        System.in.read();
+
+        System.out.println("dubbo service started");
+        new CountDownLatch(1).await();
     }
 
 }
diff --git a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml
index c5ceaa2..ccbf779 100644
--- a/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml
+++ b/dubbo-samples-configcenter/dubbo-samples-configcenter-apollo/case-configuration.yml
@@ -26,7 +26,9 @@
       - apollo.port=8080
     waitPortsBeforeRun:
       - apollo-quick-start:8080
-    checkTimeout: 100
+    waitTimeout: 100
+    depends_on:
+      - apollo-db
 
   dubbo-samples-configcenter-apollo-test:
     type: test
@@ -41,7 +43,7 @@
     waitPortsBeforeRun:
       - dubbo-samples-configcenter-apollo:2181
       - dubbo-samples-configcenter-apollo:20880
-    checkTimeout: 100
+    waitTimeout: 100
     depends_on:
       - dubbo-samples-configcenter-apollo
   
@@ -49,9 +51,9 @@
     image: nobodyiam/apollo-quick-start
     depends_on:
       - apollo-db
-#    ports:
-#      - "8080:8080"
-#      - "8070:8070"
+    expose:
+      - 8080
+      - 8070
     links:
       - apollo-db
 
@@ -60,8 +62,14 @@
     environment:
       - TZ=Asia/Shanghai
       - MYSQL_ALLOW_EMPTY_PASSWORD=yes
-#    ports:
-#      - "13306:3306"
+    expose:
+      - 3306
+    healthcheck:
+      # mysql host MUST be ip address, if the host is localhost, may connect via socket file, the port will be ignored
+      test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
     volumes:
       - ${_basedir}/src/main/resources/docker/sql:/docker-entrypoint-initdb.d
 #      - ./sql_data:/var/lib/mysql
diff --git a/dubbo-samples-local/src/main/java/org/apache/dubbo/samples/local/LocalDemo.java b/dubbo-samples-local/src/main/java/org/apache/dubbo/samples/local/LocalDemo.java
index fa6b0e0..94ea847 100644
--- a/dubbo-samples-local/src/main/java/org/apache/dubbo/samples/local/LocalDemo.java
+++ b/dubbo-samples-local/src/main/java/org/apache/dubbo/samples/local/LocalDemo.java
@@ -31,6 +31,7 @@
         new EmbeddedZooKeeper(2181, true).start();
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-demo.xml");
         context.start();
+        System.out.println("dubbo service started");
 
         DemoService demoService = context.getBean("demoService", DemoService.class);
         String hello = demoService.sayHello("world");
diff --git a/dubbo-samples-protostuff/dubbo-samples-protostuff-provider/src/main/java/org/dubbo/samples/protostuff/provider/ProviderApp.java b/dubbo-samples-protostuff/dubbo-samples-protostuff-provider/src/main/java/org/dubbo/samples/protostuff/provider/ProviderApp.java
index bbc6beb..9c34119 100644
--- a/dubbo-samples-protostuff/dubbo-samples-protostuff-provider/src/main/java/org/dubbo/samples/protostuff/provider/ProviderApp.java
+++ b/dubbo-samples-protostuff/dubbo-samples-protostuff-provider/src/main/java/org/dubbo/samples/protostuff/provider/ProviderApp.java
@@ -21,10 +21,15 @@
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 
+import java.util.concurrent.CountDownLatch;
+
 @SpringBootApplication
 public class ProviderApp {
 
-    public static void main(String[] args) {
+    public static void main(String[] args) throws Exception {
         new SpringApplicationBuilder(ProviderApp.class).web(WebApplicationType.NONE).run(args);
+
+        System.out.println("dubbo service started");
+        new CountDownLatch(1).await();
     }
 }
diff --git a/dubbo-samples-rest/src/main/java/org/apache/dubbo/samples/rest/RestProvider.java b/dubbo-samples-rest/src/main/java/org/apache/dubbo/samples/rest/RestProvider.java
index fe25055..1486b89 100644
--- a/dubbo-samples-rest/src/main/java/org/apache/dubbo/samples/rest/RestProvider.java
+++ b/dubbo-samples-rest/src/main/java/org/apache/dubbo/samples/rest/RestProvider.java
@@ -22,13 +22,17 @@
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
+import java.util.concurrent.CountDownLatch;
+
 public class RestProvider {
 
     public static void main(String[] args) throws Exception {
         new EmbeddedZooKeeper(2181, false).start();
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/rest-provider.xml"});
         context.start();
-        System.in.read();
+
+        System.out.println("dubbo service started");
+        new CountDownLatch(1).await();
     }
 
     @Configuration
diff --git a/dubbo-samples-spi-compatible/case-configuration.yml b/dubbo-samples-spi-compatible/case-configuration.yml
index 419c944..832600b 100644
--- a/dubbo-samples-spi-compatible/case-configuration.yml
+++ b/dubbo-samples-spi-compatible/case-configuration.yml
@@ -19,29 +19,29 @@
   zookeeper_port: 2181
 
 services:
-  ${project_name}:
+  dubbo-samples-spi-compatible:
     type: app
     basedir: .
     mainClass: org.apache.dubbo.samples.basic.SpiCompatibleProvider
     systemProps:
       - zookeeper.address=127.0.0.1
-      - zookeeper.port=${zookeeper_port}
-    checkPortsAfterRun:
-      - ${zookeeper_port}
+      - zookeeper.port=2181
+    checkPorts:
+      - 2181
     checkLog: "dubbo service started"
 
-  ${project_name}-test:
+  dubbo-samples-spi-compatible-test:
     type: test
     basedir: .
     tests:
       - "**/*IT.class"
     systemProps:
-      - zookeeper.address=${project_name}
-      - zookeeper.port=${zookeeper_port}
-      - dubbo.address=${project_name}
+      - zookeeper.address=dubbo-samples-spi-compatible
+      - zookeeper.port=2181
+      - dubbo.address=dubbo-samples-spi-compatible
     waitPortsBeforeRun:
-      - ${project_name}:${zookeeper_port}
+      - dubbo-samples-spi-compatible:2181
       # Disable Dubbo port check due to Dubbo-Samples-SPI-Compatible implements a new protocol ---- CompatibleProtocol
       # CompatibleProtocol will not expose a port and port check is invalid for it
     depends_on:
-      - ${project_name}
+      - dubbo-samples-spi-compatible
diff --git a/dubbo-samples-transaction/case-configuration.yml b/dubbo-samples-transaction/case-configuration.yml
index dc18ef8..8420578 100644
--- a/dubbo-samples-transaction/case-configuration.yml
+++ b/dubbo-samples-transaction/case-configuration.yml
@@ -34,6 +34,12 @@
       - 3306
     volumes:
       - ${_basedir}/src/main/resources/docker/mysql/sql:/docker-entrypoint-initdb.d
+    healthcheck:
+      # mysql host MUST be ip address, if the host is localhost, may connect via socket file, the port will be ignored
+      test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
 
   seata-server:
     #build: ${_basedir}/src/main/resources/docker/seata
@@ -101,4 +107,8 @@
       - storage-service:20882
       - account-service:20881
       - order-service:20883
+    depends_on:
+      - order-service
+      - account-service
+      - storage-service
 
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java
index d444e3c..3ed4cc7 100644
--- a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/ConfigurationImpl.java
@@ -33,6 +33,7 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -53,9 +54,9 @@
     public static final String ENV_SERVICE_TYPE = "SERVICE_TYPE";
     public static final String ENV_APP_MAIN_CLASS = "APP_MAIN_CLASS";
     public static final String ENV_WAIT_PORTS_BEFORE_RUN = "WAIT_PORTS_BEFORE_RUN";
-    public static final String ENV_CHECK_PORTS_AFTER_RUN = "CHECK_PORTS_AFTER_RUN";
+    public static final String ENV_CHECK_PORTS = "CHECK_PORTS";
     public static final String ENV_CHECK_LOG = "CHECK_LOG";
-    public static final String ENV_CHECK_TIMEOUT = "CHECK_TIMEOUT";
+    public static final String ENV_WAIT_TIMEOUT = "WAIT_TIMEOUT";
     public static final String ENV_TEST_PATTERNS = "TEST_PATTERNS";
     public static final String ENV_JAVA_OPTS = "JAVA_OPTS";
     public static final String ENV_DEBUG_OPTS = "DEBUG_OPTS";
@@ -76,6 +77,7 @@
     private int javaDebugPort = 20660;
     private int debugTimeout = 36000;
     private Set<String> debugServices = new HashSet<>();
+    private Set<String> healthcheckServices = new HashSet<>();
 
     public ConfigurationImpl() throws IOException, ConfigureFileNotFoundException {
         String configureFile = System.getProperty("configure.file");
@@ -252,7 +254,7 @@
 
                 //set check timeout
                 if (isDebug()) {
-                    service.setCheckTimeout(debugTimeout);
+                    service.setWaitTimeout(debugTimeout);
 
                     if (debugServices.contains(serviceName)) {
                         //set java remote debug opts
@@ -268,8 +270,8 @@
                         service.getPorts().add(debugPort + ":" + debugPort);
                     }
                 }
-                if (service.getCheckTimeout() > 0) {
-                    setEnv(service, ENV_CHECK_TIMEOUT, service.getCheckTimeout() + "");
+                if (service.getWaitTimeout() > 0) {
+                    setEnv(service, ENV_WAIT_TIMEOUT, service.getWaitTimeout() + "");
                 }
 
                 if ("app".equals(type)) {
@@ -284,15 +286,32 @@
                     //set SCENARIO_HOME
                     setEnv(service, ENV_SCENARIO_HOME, scenarioHome);
 
+                    boolean addHealthcheck = false;
                     //set check_log env
                     if (StringUtils.isNotBlank(service.getCheckLog())) {
+                        addHealthcheck = true;
                         setEnv(service, ENV_CHECK_LOG, service.getCheckLog());
                     }
 
                     //set check ports
-                    if (isNotEmpty(service.getCheckPortsAfterRun())) {
-                        String str = convertAddrPortsToString(service.getCheckPortsAfterRun());
-                        setEnv(service, ENV_CHECK_PORTS_AFTER_RUN, str);
+                    if (isNotEmpty(service.getCheckPorts())) {
+                        addHealthcheck = true;
+                        String str = convertAddrPortsToString(service.getCheckPorts());
+                        setEnv(service, ENV_CHECK_PORTS, str);
+                    }
+
+                    //add healthcheck
+                    if (addHealthcheck) {
+                        if (service.getHealthcheck() == null) {
+                            Map<String, Object> healthcheckMap = new LinkedHashMap<>();
+                            service.setHealthcheck(healthcheckMap);
+                        }
+                        Map<String, Object> healthcheck = service.getHealthcheck();
+                        healthcheck.putIfAbsent("test", Arrays.asList("CMD", "/usr/local/dubbo/healthcheck.sh"));
+                        healthcheck.putIfAbsent("interval", "5s");
+                        healthcheck.putIfAbsent("timeout", "5s");
+                        healthcheck.putIfAbsent("retries", 20);
+                        healthcheck.putIfAbsent("start_period", "60s");
                     }
                 } else if ("test".equals(type)) {
                     String mainClass = service.getMainClass();
@@ -330,6 +349,9 @@
                 appendEnv(service, ENV_JAVA_OPTS, str);
             }
 
+            if (service.getHealthcheck() != null) {
+                healthcheckServices.add(serviceName);
+            }
         }
     }
 
@@ -579,12 +601,36 @@
             service.setHostname(dependency.getHostname());
             service.setExpose(dependency.getExpose());
             service.setPorts(dependency.getPorts());
-            service.setDepends_on(dependency.getDepends_on());
-            service.setLinks(dependency.getDepends_on());
             service.setEntrypoint(dependency.getEntrypoint());
-            service.setHealthcheck(dependency.getHealthcheck());
+            service.setLinks(dependency.getDepends_on());
+
+            //convert depends_on to map
+            if (dependency.getDepends_on() != null) {
+                Map<String, String> dependsOnMap = new LinkedHashMap<>();
+                service.setDepends_on(dependsOnMap);
+                for (String serviceName : dependency.getDepends_on()) {
+                    if (healthcheckServices.contains(serviceName)) {
+                        dependsOnMap.put(serviceName, "{condition: service_healthy}");
+                    } else {
+                        dependsOnMap.put(serviceName, "{condition: service_started}");
+                    }
+                }
+            }
+
+            //convert healthcheck to string map
+            if (dependency.getHealthcheck() != null) {
+                Yaml yaml = new Yaml();
+                Map<String, Object> healthcheckMap = dependency.getHealthcheck();
+                Map<String, String> newMap = new LinkedHashMap<>();
+                for (Map.Entry<String, Object> entry : healthcheckMap.entrySet()) {
+                    String value = yaml.dump(entry.getValue());
+                    newMap.put(entry.getKey(), value.trim());
+                }
+                service.setHealthcheck(newMap);
+            }
             service.setEnvironment(dependency.getEnvironment());
             service.setVolumes(dependency.getVolumes());
+            service.setVolumes_from(dependency.getVolumes_from());
             service.setRemoveOnExit(dependency.isRemoveOnExit());
             services.add(service);
         });
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java
index 0af47b7..fad5f11 100644
--- a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/DockerService.java
@@ -18,6 +18,7 @@
 package org.apache.dubbo.scenario.builder.vo;
 
 import java.util.List;
+import java.util.Map;
 
 public class DockerService {
     private String name;
@@ -30,10 +31,11 @@
     private List<String> expose;
     private List<String> ports;
     private List<String> entrypoint;
-    private List<String> healthcheck;
-    private List<String> depends_on;
+    private Map<String, String> healthcheck;
+    private Map<String, String> depends_on;
     private List<String> environment;
     private List<String> volumes;
+    private List<String> volumes_from;
 
     public String getName() {
         return name;
@@ -99,19 +101,19 @@
         this.entrypoint = entrypoint;
     }
 
-    public List<String> getHealthcheck() {
+    public Map<String, String> getHealthcheck() {
         return healthcheck;
     }
 
-    public void setHealthcheck(List<String> healthcheck) {
+    public void setHealthcheck(Map<String, String> healthcheck) {
         this.healthcheck = healthcheck;
     }
 
-    public List<String> getDepends_on() {
+    public Map<String, String> getDepends_on() {
         return depends_on;
     }
 
-    public void setDepends_on(List<String> depends_on) {
+    public void setDepends_on(Map<String, String> depends_on) {
         this.depends_on = depends_on;
     }
 
@@ -131,6 +133,14 @@
         this.volumes = volumes;
     }
 
+    public List<String> getVolumes_from() {
+        return volumes_from;
+    }
+
+    public void setVolumes_from(List<String> volumes_from) {
+        this.volumes_from = volumes_from;
+    }
+
     public List<String> getPorts() {
         return ports;
     }
@@ -155,6 +165,7 @@
                 ", depends_on=" + depends_on +
                 ", environment=" + environment +
                 ", volumes=" + volumes +
+                ", volumes_from=" + volumes_from +
                 '}';
     }
 }
diff --git a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java
index e4b2c42..e94078c 100644
--- a/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java
+++ b/test/dubbo-scenario-builder/src/main/java/org/apache/dubbo/scenario/builder/vo/ServiceComponent.java
@@ -19,6 +19,7 @@
 
 
 import java.util.List;
+import java.util.Map;
 
 public class ServiceComponent {
     private String image;
@@ -35,16 +36,16 @@
     private List<String> volumes;
     private List<String> volumes_from;
     private List<String> depends_on;
-    private List<String> healthcheck;
+    private Map<String, Object> healthcheck;
 
     // app attrs
     private String type;
     private String basedir;
     private String mainClass;
     private List<String> waitPortsBeforeRun;
-    private List<String> checkPortsAfterRun;
+    private int waitTimeout;
+    private List<String> checkPorts;
     private String checkLog;
-    private int checkTimeout;
     private List<String> tests;
     private List<String> systemProps;
     private List<String> jvmFlags;
@@ -121,11 +122,11 @@
         this.depends_on = depends_on;
     }
 
-    public List<String> getHealthcheck() {
+    public Map<String, Object> getHealthcheck() {
         return healthcheck;
     }
 
-    public void setHealthcheck(List<String> healthcheck) {
+    public void setHealthcheck(Map<String, Object> healthcheck) {
         this.healthcheck = healthcheck;
     }
 
@@ -153,12 +154,12 @@
         this.mainClass = mainClass;
     }
 
-    public List<String> getCheckPortsAfterRun() {
-        return checkPortsAfterRun;
+    public List<String> getCheckPorts() {
+        return checkPorts;
     }
 
-    public void setCheckPortsAfterRun(List<String> checkPortsAfterRun) {
-        this.checkPortsAfterRun = checkPortsAfterRun;
+    public void setCheckPorts(List<String> checkPorts) {
+        this.checkPorts = checkPorts;
     }
 
     public List<String> getWaitPortsBeforeRun() {
@@ -177,12 +178,12 @@
         this.checkLog = checkLog;
     }
 
-    public int getCheckTimeout() {
-        return checkTimeout;
+    public int getWaitTimeout() {
+        return waitTimeout;
     }
 
-    public void setCheckTimeout(int checkTimeout) {
-        this.checkTimeout = checkTimeout;
+    public void setWaitTimeout(int waitTimeout) {
+        this.waitTimeout = waitTimeout;
     }
 
     public List<String> getTests() {
@@ -262,9 +263,9 @@
                 ", basedir='" + basedir + '\'' +
                 ", mainClass='" + mainClass + '\'' +
                 ", waitPortsBeforeRun=" + waitPortsBeforeRun +
-                ", checkPortsAfterRun=" + checkPortsAfterRun +
+                ", waitTimeout=" + waitTimeout +
+                ", checkPorts=" + checkPorts +
                 ", checkLog='" + checkLog + '\'' +
-                ", checkTimeout=" + checkTimeout +
                 ", tests=" + tests +
                 ", systemProps=" + systemProps +
                 ", jvmFlags=" + jvmFlags +
diff --git a/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml b/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml
index d54d191..fe8a946 100644
--- a/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml
+++ b/test/dubbo-scenario-builder/src/main/resources/configs/app-builtin-zookeeper.yml
@@ -39,10 +39,10 @@
     systemProps:
       - zookeeper.address=${project_name}
       - zookeeper.port=${zookeeper_port}
-#    checkPortsAfterRun:
-#      - ${zookeeper_port}
-#      - ${dubbo_port}
-#    checkLog: "dubbo service started"
+    checkPorts:
+      - ${zookeeper_port}
+      - ${dubbo_port}
+    checkLog: "dubbo service started"
 
   ${project_name}-test:
     type: test
diff --git a/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml b/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml
index 7a5fd75..ac0f042 100644
--- a/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml
+++ b/test/dubbo-scenario-builder/src/main/resources/configs/app-external-zookeeper.yml
@@ -44,9 +44,9 @@
       - zookeeper.port=2181
     waitPortsBeforeRun:
       - zookeeper:2181
-#    checkPortsAfterRun:
-#      - ${dubbo_port}
-#    checkLog: "dubbo service started"
+    checkPorts:
+      - ${dubbo_port}
+    checkLog: "dubbo service started"
 
   ${project_name}-test:
     type: test
diff --git a/test/dubbo-scenario-builder/src/main/resources/docker-compose.template b/test/dubbo-scenario-builder/src/main/resources/docker-compose.template
index bdba61f..e325f70 100644
--- a/test/dubbo-scenario-builder/src/main/resources/docker-compose.template
+++ b/test/dubbo-scenario-builder/src/main/resources/docker-compose.template
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-version: '2.1'
+version: '2.4'
 
 networks:
    default:
@@ -68,8 +68,8 @@
         </#if>
         <#if service.depends_on??>
         depends_on:
-        <#list service.depends_on as item>
-            - ${item}
+        <#list service.depends_on as name,value>
+            ${name}: ${value}
         </#list>
         </#if>
         <#if service.entrypoint??>
@@ -80,9 +80,9 @@
         </#if>
         <#if service.healthcheck??>
         healthcheck:
-        <#list service.healthcheck as item>
-            ${item}
-        </#list>
+          <#list service.healthcheck as key,value>
+            ${key}: ${value}
+          </#list>
         </#if>
         <#if service.links??>
         links:
diff --git a/test/dubbo-scenario-builder/src/main/resources/scenario.sh b/test/dubbo-scenario-builder/src/main/resources/scenario.sh
index 757e89d..5a29687 100644
--- a/test/dubbo-scenario-builder/src/main/resources/scenario.sh
+++ b/test/dubbo-scenario-builder/src/main/resources/scenario.sh
@@ -32,32 +32,67 @@
 test_service_name="${test_service_name}_1"
 network_name="${network_name}"
 
-function redirect_container_logs() {
-  #copy logs
-  echo "Redirecting container logs .." >> $scenario_log
+service_names=( \
+<#list services as service>
+  "${service.name}" \
+</#list>
+  )
 
-  if [ "$debug_mode" == "1" ]; then
-    # redirect container logs to file and display debug message
-    <#list services as service>
-    service_name="${service.name}"
-    <#noparse>
-    docker logs -f ${project_name}_${service_name}_1 2>&1 | tee $SCENARIO_HOME/logs/${service_name}.log | grep "dt_socket" &
-    </#noparse>
+service_size=${services?size}
 
-    </#list>
-  else
-    # redirect container logs to file
-    <#list services as service>
-    service_name="${service.name}"
-    <#noparse>
-    docker logs -f ${project_name}_${service_name}_1 &> $SCENARIO_HOME/logs/${service_name}.log &
-    </#noparse>
-
-    </#list>
-  fi
-}
+export service_names=$service_names
+export service_size=$service_size
 
 <#noparse>
+function redirect_all_container_logs() {
+  #redirect logs
+  echo "Redirecting all container logs: ${service_names[@]}  .." >> $scenario_log
+
+  while [ 1==1 ]; do
+    redirect_count=0
+    for service_name in ${service_names[@]};do
+      redirect_container_logs "$service_name"
+      result=$?
+      if [ "$result" -eq 0 ];then
+        redirect_count=$(( redirect_count + 1 ))
+      fi
+    done
+
+    if [ "$redirect_count" == $service_size ];then
+      echo "Redirect all containers logs."  >> $scenario_log
+      break
+    fi
+
+    sleep 3
+  done
+}
+
+function redirect_container_logs() {
+  service_name=$1
+  container_name=${project_name}_${service_name}_1
+  # only redirect once
+  ps -ef | grep "docker logs -f $container_name" | grep -v grep > /dev/null
+  result=$?
+  if [ $result -eq 0 ]; then
+    return 0
+  fi
+
+  container_id=`docker ps -qf "name=${container_name}"`
+  if [[ -z "${container_id}" ]]; then
+    return 1
+  fi
+
+  echo "Redirect container logs: $container_name" >> $scenario_log
+  if [ "$debug_mode" == "1" ]; then
+    # redirect container logs to file and display debug message
+    docker logs -f $container_name 2>&1 | tee $SCENARIO_HOME/logs/${service_name}.log | grep "dt_socket" &
+  else
+    docker logs -f $container_name &> $SCENARIO_HOME/logs/${service_name}.log &
+  fi
+  return 0
+
+}
+
 function wait_container_exit() {
   container_name=$1
   start=$2
@@ -66,12 +101,14 @@
   # check and get exit code
   while [ 1 = 1 ];
   do
+    sleep 2
     status=`docker inspect $container_name --format='{{.State.Status}}'`
-    result=$?
-    if [ $result -ne 0 ];then
-      echo "check container status failure: $result"
-      return 1
-    fi
+    # test container may pending start cause by depends_on condition
+#    result=$?
+#    if [ $result -ne 0 ];then
+#      echo "check container status failure: $result"
+#      return 1
+#    fi
     if [ "$status" == "exited" ];then
         return 0
     fi
@@ -81,7 +118,6 @@
       echo "wait for container is timeout: $duration s"
       return 1
     fi
-    sleep 2
   done
 }
 
@@ -105,18 +141,21 @@
 echo "[$scenario_name] Removing containers .." | tee -a $scenario_log
 docker-compose -p ${project_name} -f ${compose_file} rm -f 2>&1 | tee -a $scenario_log > /dev/null
 
+#run async, cause depends_on service healthy blocking docker-compose up
+redirect_all_container_logs &
+
 # complete pull fail interactive by <<< "NN"
 echo "[$scenario_name] Starting containers .." | tee -a $scenario_log
 docker-compose -p ${project_name} -f ${compose_file} up -d 2>&1 <<< "NNN" | tee -a $scenario_log > /dev/null
 
-sleep 2
-redirect_container_logs
+sleep 5
 
-container_id=`docker ps -qf "name=${container_name}"`
-if [[ -z "${container_id}" ]]; then
-    echo "[$scenario_name] docker startup failure!" | tee -a $scenario_log
-    status=1
-else
+# test container may pending start cause by depends_on condition
+#container_id=`docker ps -qf "name=${container_name}"`
+#if [[ -z "${container_id}" ]]; then
+#    echo "[$scenario_name] docker startup failure!" | tee -a $scenario_log
+#    status=1
+#else
     echo "[$scenario_name] Waiting for test container .." | tee -a $scenario_log
     # check and get exit code
     wait_container_exit ${container_name} $start $timeout
@@ -138,11 +177,17 @@
     echo "[$scenario_name] Stopping containers .." | tee -a $scenario_log
     docker-compose -p ${project_name} -f ${compose_file} kill 2>&1 | tee -a $scenario_log > /dev/null
 
-fi
+#fi
 
 if [[ $status == 0 ]];then
     docker-compose -p $project_name -f $compose_file rm -f 2>&1 | tee -a $scenario_log > /dev/null
     ${removeImagesScript}
+else
+    for service_name in ${service_names[@]};do
+        echo "docker inspect ${project_name}_${service_name}_1 :"
+        docker inspect ${project_name}_${service_name}_1 >> $scenario_log
+        echo ""
+    done
 fi
 
 # rm network
diff --git a/test/dubbo-test-runner/src/docker/Dockerfile b/test/dubbo-test-runner/src/docker/Dockerfile
index 3bd2988..d7355c2 100644
--- a/test/dubbo-test-runner/src/docker/Dockerfile
+++ b/test/dubbo-test-runner/src/docker/Dockerfile
@@ -29,20 +29,23 @@
 
 # JAVA_OPTS/DEBUG_OPTS: Jvm flags
 # SERVICE_TYPE: app, test
-# CHECK_TIMEOUT: check ports/log timeout
+# WAIT_PORTS_BEFORE_RUN: wait ports before run
+# WAIT_TIMEOUT: wait ports timeout
 # TEST_PATTERNS: test class pattern
 # classpath: assert mounting $project_dir/target:/usr/local/dubbo/app/
+# CHECK_PORTS: health check ports
+# CHECK_LOG: health check log
 ENV JAVA_OPTS="" \
     DEBUG_OPTS="" \
     TEST_CLASSES_DIR="/usr/local/dubbo/app/test-classes" \
     APP_CLASSES_DIR="/usr/local/dubbo/app/classes" \
     APP_DEPENDENCY_DIR="/usr/local/dubbo/app/dependency" \
-    CHECK_TIMEOUT=60 \
     SERVICE_NAME="" \
     SERVICE_TYPE="app" \
+    WAIT_TIMEOUT=60 \
     WAIT_PORTS_BEFORE_RUN="" \
     APP_MAIN_CLASS="" \
-    CHECK_PORTS_AFTER_RUN="" \
+    CHECK_PORTS="" \
     CHECK_LOG="" \
     TEST_PATTERNS="**/*IT.class"
 
diff --git a/test/dubbo-test-runner/src/docker/healthcheck.sh b/test/dubbo-test-runner/src/docker/healthcheck.sh
new file mode 100755
index 0000000..926ff88
--- /dev/null
+++ b/test/dubbo-test-runner/src/docker/healthcheck.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+if [ "$SERVICE_NAME" == "" ]; then
+  echo "Missing env 'SERVICE_NAME'"
+  return 1
+fi
+
+if [ "$CHECK_LOG" == "" ] && [ "$CHECK_PORTS" == "" ]; then
+  echo "Require at least one of the env: 'CHECK_LOG' or 'CHECK_PORTS'"
+  return 1
+fi
+
+DIR=/usr/local/dubbo/
+cd $DIR
+
+LOG_DIR=/usr/local/dubbo/logs
+if [ ! -d $LOG_DIR ];then
+  mkdir -p $LOG_DIR
+fi
+LOG_FILE=$LOG_DIR/$SERVICE_NAME.log
+
+source $DIR/utils.sh
+
+# check ports
+if [ "$CHECK_PORTS" != "" ]; then
+  split_and_check_tcp_ports "$CHECK_PORTS" $SECONDS 1
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "check ports failure"
+    exit $result
+  fi
+fi
+
+# check log
+if [ "$CHECK_LOG" != "" ]; then
+  check_log "$LOG_FILE" "$CHECK_LOG" $SECONDS 1
+  result=$?
+  if [ $result -ne 0 ]; then
+    echo "check log failure"
+    exit $result
+  fi
+fi
+
diff --git a/test/dubbo-test-runner/src/docker/run-dubbo-app.sh b/test/dubbo-test-runner/src/docker/run-dubbo-app.sh
index ae4d089..cf4d411 100755
--- a/test/dubbo-test-runner/src/docker/run-dubbo-app.sh
+++ b/test/dubbo-test-runner/src/docker/run-dubbo-app.sh
@@ -13,8 +13,7 @@
 # wait ports before run app: WAIT_PORTS_BEFORE_RUN=host:port;host:port
 if [ "$WAIT_PORTS_BEFORE_RUN" != "" ]; then
   echo "Waiting ports before run app .."
-  # check ports
-  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $CHECK_TIMEOUT
+  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $WAIT_TIMEOUT
   result=$?
   if [ $result -ne 0 ]; then
     echo "Wait ports before run app failure"
@@ -27,18 +26,6 @@
 java $JAVA_OPTS $DEBUG_OPTS -cp "$APP_CLASSES_DIR:$APP_DEPENDENCY_DIR/*" $APP_MAIN_CLASS 2>&1 &
 pid=$!
 
-sleep 20
-
-# check ports after run
-if [ "$CHECK_PORTS_AFTER_RUN" != "" ]; then
-  split_and_check_tcp_ports "$CHECK_PORTS_AFTER_RUN" $start $CHECK_TIMEOUT
-  result=$?
-  if [ $result -ne 0 ]; then
-    echo "Wait ports after run app failure"
-    exit $result
-  fi
-fi
-
 echo "Wait for process to exit: $pid .."
 wait $pid
 result=$?
diff --git a/test/dubbo-test-runner/src/docker/run-dubbo-test.sh b/test/dubbo-test-runner/src/docker/run-dubbo-test.sh
index 47a1de9..2783282 100755
--- a/test/dubbo-test-runner/src/docker/run-dubbo-test.sh
+++ b/test/dubbo-test-runner/src/docker/run-dubbo-test.sh
@@ -15,7 +15,7 @@
 if [ "$WAIT_PORTS_BEFORE_RUN" != "" ]; then
   echo "Waiting ports before run test .."
   # check ports
-  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $CHECK_TIMEOUT
+  split_and_check_tcp_ports "$WAIT_PORTS_BEFORE_RUN" $SECONDS $WAIT_TIMEOUT
   result=$?
   if [ $result -ne 0 ]; then
     echo "Wait ports before run test failure"
diff --git a/test/dubbo-test-runner/src/docker/run.sh b/test/dubbo-test-runner/src/docker/run.sh
index 08fcaf5..fa4e938 100755
--- a/test/dubbo-test-runner/src/docker/run.sh
+++ b/test/dubbo-test-runner/src/docker/run.sh
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+echo "Start at: $(date "+%Y-%m-%d %H:%M:%S")"
+
 DIR=/usr/local/dubbo/
 cd $DIR