You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2019/06/30 12:44:33 UTC

[skywalking] branch master updated: Add end to end tests (#2935)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new d1a521b  Add end to end tests (#2935)
d1a521b is described below

commit d1a521b705a27e92aa844f03e9d90131e47391cd
Author: kezhenxu94 <ke...@163.com>
AuthorDate: Sun Jun 30 20:44:25 2019 +0800

    Add end to end tests (#2935)
    
    e2e test setup. cc @hanahmily @peng-yongsheng @IanCao @zhaoyuguang @JaredTan95
    
    # e2e test case
    One single standalone Spring service, accessing H2 database. This service has been installed the agent.
    OAP runs in single mode with H2 storage too.
    
    The e2e test will verify the results through GraphQL query
    1. Traces exist.
    2. Service/Service Instance/Endpoint exist and have expected metrics.
    3. Topology exists and match expected.
---
 Jenkinsfile-E2E                                    |  60 +++++
 apm-dist/bin/oapService.sh                         |   4 +-
 apm-dist/bin/webappService.sh                      |   4 +-
 test/e2e/e2e-base/pom.xml                          |  45 ++++
 .../org/apache/skywalking/e2e/AbstractQuery.java   |  90 +++++++
 .../org/apache/skywalking/e2e/GQLResponse.java     |  43 +++
 .../apache/skywalking/e2e/SimpleQueryClient.java   | 202 ++++++++++++++
 .../e2e/metrics/AtLeastOneOfMetricsMatcher.java    |  62 +++++
 .../org/apache/skywalking/e2e/metrics/Metrics.java |  44 ++++
 .../apache/skywalking/e2e/metrics/MetricsData.java |  41 +++
 .../skywalking/e2e/metrics/MetricsQuery.java       |  71 +++++
 .../skywalking/e2e/metrics/MetricsValue.java       |  42 +++
 .../e2e/metrics/MetricsValueMatcher.java           |  52 ++++
 .../org/apache/skywalking/e2e/service/Service.java |  53 ++++
 .../skywalking/e2e/service/ServiceMatcher.java     |  75 ++++++
 .../skywalking/e2e/service/ServicesData.java       |  36 +++
 .../skywalking/e2e/service/ServicesMatcher.java    |  53 ++++
 .../skywalking/e2e/service/ServicesQuery.java      |  27 ++
 .../skywalking/e2e/service/endpoint/Endpoint.java  |  51 ++++
 .../e2e/service/endpoint/EndpointMatcher.java      |  57 ++++
 .../e2e/service/endpoint/EndpointQuery.java        |  53 ++++
 .../skywalking/e2e/service/endpoint/Endpoints.java |  43 +++
 .../e2e/service/endpoint/EndpointsMatcher.java     |  61 +++++
 .../skywalking/e2e/service/instance/Attribute.java |  51 ++++
 .../e2e/service/instance/AttributeMatcher.java     |  63 +++++
 .../skywalking/e2e/service/instance/Instance.java  |  65 +++++
 .../e2e/service/instance/InstanceMatcher.java      | 113 ++++++++
 .../skywalking/e2e/service/instance/Instances.java |  36 +++
 .../e2e/service/instance/InstancesMatcher.java     |  55 ++++
 .../e2e/service/instance/InstancesQuery.java       |  80 ++++++
 .../java/org/apache/skywalking/e2e/topo/Call.java  |  67 +++++
 .../apache/skywalking/e2e/topo/CallMatcher.java    | 104 ++++++++
 .../java/org/apache/skywalking/e2e/topo/Node.java  |  65 +++++
 .../apache/skywalking/e2e/topo/NodeMatcher.java    |  96 +++++++
 .../org/apache/skywalking/e2e/topo/TopoData.java   |  53 ++++
 .../apache/skywalking/e2e/topo/TopoMatcher.java    |  84 ++++++
 .../org/apache/skywalking/e2e/topo/TopoQuery.java  |  27 ++
 .../apache/skywalking/e2e/topo/TopoResponse.java   |  34 +++
 .../org/apache/skywalking/e2e/trace/Trace.java     |  95 +++++++
 .../apache/skywalking/e2e/trace/TraceMatcher.java  | 171 ++++++++++++
 .../apache/skywalking/e2e/trace/TracesData.java    |  49 ++++
 .../apache/skywalking/e2e/trace/TracesMatcher.java |  53 ++++
 .../apache/skywalking/e2e/trace/TracesQuery.java   |  92 +++++++
 .../e2e/verification/AbstractMatcher.java          |  71 +++++
 test/e2e/e2e-base/src/main/resources/endpoints.gql |  28 ++
 test/e2e/e2e-base/src/main/resources/instances.gql |  36 +++
 test/e2e/e2e-base/src/main/resources/metrics.gql   |  36 +++
 test/e2e/e2e-base/src/main/resources/services.gql  |  31 +++
 test/e2e/e2e-base/src/main/resources/topo.gql      |  41 +++
 test/e2e/e2e-base/src/main/resources/traces.gql    |  42 +++
 .../org/apache/skywalking/e2e/TestMatcher.java     |  92 +++++++
 .../apache/skywalking/e2e/TestMetricsMatcher.java  |  52 ++++
 test/e2e/e2e-base/src/test/resources/test.yml      |  31 +++
 test/e2e/e2e-single-service/pom.xml                | 144 ++++++++++
 .../e2e/sample/client/SampleClientApplication.java |  34 +++
 .../e2e/sample/client/TestController.java          |  48 ++++
 .../apache/skywalking/e2e/sample/client/User.java  |  56 ++++
 .../skywalking/e2e/sample/client/UserRepo.java     |  27 ++
 .../src/main/resources/application.yml             |  35 +++
 .../skywalking/e2e/SampleVerificationITCase.java   | 292 +++++++++++++++++++++
 ...king.e2e.SampleVerificationITCase.endpoints.yml |  27 ++
 ...king.e2e.SampleVerificationITCase.instances.yml |  34 +++
 ...lking.e2e.SampleVerificationITCase.services.yml |  25 ++
 ...kywalking.e2e.SampleVerificationITCase.topo.yml |  46 ++++
 ...walking.e2e.SampleVerificationITCase.traces.yml |  31 +++
 test/e2e/pom.xml                                   | 169 ++++++++++++
 66 files changed, 4146 insertions(+), 4 deletions(-)

diff --git a/Jenkinsfile-E2E b/Jenkinsfile-E2E
new file mode 100644
index 0000000..d43016e
--- /dev/null
+++ b/Jenkinsfile-E2E
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ */
+
+pipeline {
+    agent {
+        label 'xenial'
+    }
+
+    tools {
+        jdk 'JDK 1.8 (latest)'
+    }
+
+    stages {
+        stage('Checkout Source Code') {
+            steps {
+                deleteDir()
+                checkout scm
+                sh 'git submodule update --init'
+            }
+        }
+
+        stage('Prepare Distribution Package') {
+            steps {
+                sh './mvnw -DskipTests clean package'
+                sh 'tar -zxf dist/apache-skywalking-apm-bin.tar.gz -C dist'
+            }
+        }
+
+        stage('Run End-to-End Tests') {
+            steps {
+                sh './mvnw -Dbuild.id=${BUILD_ID} -f test/e2e/pom.xml clean verify'
+            }
+        }
+    }
+
+    post {
+        always {
+            // "Abort old build on update" will interrupt the job completely,
+            // we need to clean up when there are containers started by the e2e tests
+            sh 'docker ps'
+            sh 'docker ps | grep -e "skywalking-e2e-container-${BUILD_ID}" | awk \'{print $1}\' | xargs --no-run-if-empty docker stop'
+            deleteDir()
+        }
+    }
+}
diff --git a/apm-dist/bin/oapService.sh b/apm-dist/bin/oapService.sh
index ed49bc9..962691b 100644
--- a/apm-dist/bin/oapService.sh
+++ b/apm-dist/bin/oapService.sh
@@ -20,10 +20,10 @@ PRG="$0"
 PRGDIR=`dirname "$PRG"`
 [ -z "$OAP_HOME" ] && OAP_HOME=`cd "$PRGDIR/.." >/dev/null; pwd`
 
-OAP_LOG_DIR="${OAP_HOME}/logs"
+OAP_LOG_DIR="${OAP_LOG_DIR:-${OAP_HOME}/logs}"
 JAVA_OPTS=" -Xms256M -Xmx512M"
 
-if [ ! -d "${OAP_HOME}/logs" ]; then
+if [ ! -d "${OAP_LOG_DIR}" ]; then
     mkdir -p "${OAP_LOG_DIR}"
 fi
 
diff --git a/apm-dist/bin/webappService.sh b/apm-dist/bin/webappService.sh
index df0040d..890ea48 100644
--- a/apm-dist/bin/webappService.sh
+++ b/apm-dist/bin/webappService.sh
@@ -20,11 +20,11 @@ PRG="$0"
 PRGDIR=`dirname "$PRG"`
 [ -z "$WEBAPP_HOME" ] && WEBAPP_HOME=`cd "$PRGDIR/.." >/dev/null; pwd`
 
-WEBAPP_LOG_DIR="${WEBAPP_HOME}/logs"
+WEBAPP_LOG_DIR="${WEBAPP_LOG_DIR:-${WEBAPP_HOME}/logs}"
 JAVA_OPTS=" -Xms256M -Xmx512M"
 JAR_PATH="${WEBAPP_HOME}/webapp"
 
-if [ ! -d "${WEBAPP_HOME}/logs" ]; then
+if [ ! -d "${WEBAPP_LOG_DIR}" ]; then
     mkdir -p "${WEBAPP_LOG_DIR}"
 fi
 
diff --git a/test/e2e/e2e-base/pom.xml b/test/e2e/e2e-base/pom.xml
new file mode 100644
index 0000000..6979adb
--- /dev/null
+++ b/test/e2e/e2e-base/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<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">
+    <parent>
+        <artifactId>apache-skywalking-e2e</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>e2e-base</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>${snake.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-yaml</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/AbstractQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/AbstractQuery.java
new file mode 100644
index 0000000..3bb8e47
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/AbstractQuery.java
@@ -0,0 +1,90 @@
+/*
+ * 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.skywalking.e2e;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author kezhenxu94
+ */
+public abstract class AbstractQuery<T extends AbstractQuery<?>> {
+    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmmss");
+    private static final DateTimeFormatter MINUTE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm");
+
+    private String start;
+    private String end;
+    private String step = "SECOND";
+
+    public String start() {
+        if (start != null) {
+            return start;
+        }
+        return "SECOND".equals(step())
+            ? LocalDateTime.now(ZoneOffset.UTC).minusMinutes(5).format(TIME_FORMATTER)
+            : LocalDateTime.now(ZoneOffset.UTC).minusMinutes(5).format(MINUTE_TIME_FORMATTER);
+    }
+
+    public T start(String start) {
+        this.start = start;
+        return (T) this;
+    }
+
+    public T start(LocalDateTime start) {
+        if ("MINUTE".equals(step())) {
+            this.start = start.format(MINUTE_TIME_FORMATTER);
+        } else if ("SECOND".equals(step())) {
+            this.start = start.format(TIME_FORMATTER);
+        }
+        return (T) this;
+    }
+
+    public String end() {
+        if (end != null) {
+            return end;
+        }
+        return "SECOND".equals(step())
+            ? LocalDateTime.now(ZoneOffset.UTC).format(TIME_FORMATTER)
+            : LocalDateTime.now(ZoneOffset.UTC).format(MINUTE_TIME_FORMATTER);
+    }
+
+    public AbstractQuery end(String end) {
+        this.end = end;
+        return this;
+    }
+
+    public T end(LocalDateTime end) {
+        if ("MINUTE".equals(step())) {
+            this.end = end.format(MINUTE_TIME_FORMATTER);
+        } else if ("SECOND".equals(step())) {
+            this.end = end.format(TIME_FORMATTER);
+        }
+        return (T) this;
+    }
+
+    public String step() {
+        return step;
+    }
+
+    public T step(String step) {
+        this.step = step;
+        return (T) this;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/GQLResponse.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/GQLResponse.java
new file mode 100644
index 0000000..134f10b
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/GQLResponse.java
@@ -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.skywalking.e2e;
+
+/**
+ * GraphQL response for easily test
+ *
+ * @author kezhenxu94
+ */
+public class GQLResponse<T> {
+    private T data;
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(final T data) {
+        this.data = data;
+    }
+
+    @Override
+    public String toString() {
+        return "GQLResponse{" +
+            "data=" + data +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
new file mode 100644
index 0000000..1a12c79
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/SimpleQueryClient.java
@@ -0,0 +1,202 @@
+/*
+ * 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.skywalking.e2e;
+
+import com.google.common.io.Resources;
+import org.apache.skywalking.e2e.metrics.Metrics;
+import org.apache.skywalking.e2e.metrics.MetricsData;
+import org.apache.skywalking.e2e.metrics.MetricsQuery;
+import org.apache.skywalking.e2e.service.Service;
+import org.apache.skywalking.e2e.service.ServicesData;
+import org.apache.skywalking.e2e.service.ServicesQuery;
+import org.apache.skywalking.e2e.service.endpoint.EndpointQuery;
+import org.apache.skywalking.e2e.service.endpoint.Endpoints;
+import org.apache.skywalking.e2e.service.instance.Instances;
+import org.apache.skywalking.e2e.service.instance.InstancesQuery;
+import org.apache.skywalking.e2e.topo.TopoData;
+import org.apache.skywalking.e2e.topo.TopoQuery;
+import org.apache.skywalking.e2e.topo.TopoResponse;
+import org.apache.skywalking.e2e.trace.Trace;
+import org.apache.skywalking.e2e.trace.TracesData;
+import org.apache.skywalking.e2e.trace.TracesQuery;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author kezhenxu94
+ */
+public class SimpleQueryClient {
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private final String endpointUrl;
+
+    public SimpleQueryClient(String endpointUrl) {
+        this.endpointUrl = endpointUrl;
+    }
+
+    public List<Trace> traces(final TracesQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("traces.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end())
+            .replace("{step}", query.step())
+            .replace("{traceState}", query.traceState())
+            .replace("{pageNum}", query.pageNum())
+            .replace("{pageSize}", query.pageSize())
+            .replace("{needTotal}", query.needTotal())
+            .replace("{queryOrder}", query.queryOrder());
+        final ResponseEntity<GQLResponse<TracesData>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<TracesData>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData().getTraces().getData();
+    }
+
+    public List<Service> services(final ServicesQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("services.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end())
+            .replace("{step}", query.step());
+        final ResponseEntity<GQLResponse<ServicesData>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<ServicesData>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData().getServices();
+    }
+
+    public Instances instances(final InstancesQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("instances.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{serviceId}", query.serviceId())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end())
+            .replace("{step}", query.step());
+        final ResponseEntity<GQLResponse<Instances>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<Instances>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData();
+    }
+
+    public Endpoints endpoints(final EndpointQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("endpoints.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{serviceId}", query.serviceId());
+        final ResponseEntity<GQLResponse<Endpoints>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<Endpoints>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData();
+    }
+
+    public TopoData topo(final TopoQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("topo.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{step}", query.step())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end());
+        final ResponseEntity<GQLResponse<TopoResponse>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<TopoResponse>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData().getTopo();
+    }
+
+    public Metrics metrics(final MetricsQuery query) throws Exception {
+        final URL queryFileUrl = Resources.getResource("metrics.gql");
+        final String queryString = Resources.readLines(queryFileUrl, Charset.forName("UTF8"))
+            .stream()
+            .filter(it -> !it.startsWith("#"))
+            .collect(Collectors.joining())
+            .replace("{step}", query.step())
+            .replace("{start}", query.start())
+            .replace("{end}", query.end())
+            .replace("{metricsName}", query.metricsName())
+            .replace("{id}", query.id());
+        final ResponseEntity<GQLResponse<MetricsData>> responseEntity = restTemplate.exchange(
+            new RequestEntity<>(queryString, HttpMethod.POST, URI.create(endpointUrl)),
+            new ParameterizedTypeReference<GQLResponse<MetricsData>>() {
+            }
+        );
+
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
+            throw new RuntimeException("Response status != 200, actual: " + responseEntity.getStatusCode());
+        }
+
+        return Objects.requireNonNull(responseEntity.getBody()).getData().getMetrics();
+    }
+
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/AtLeastOneOfMetricsMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/AtLeastOneOfMetricsMatcher.java
new file mode 100644
index 0000000..5a47b00
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/AtLeastOneOfMetricsMatcher.java
@@ -0,0 +1,62 @@
+/*
+ * 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.skywalking.e2e.metrics;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+import org.assertj.core.api.Condition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public class AtLeastOneOfMetricsMatcher extends AbstractMatcher<Metrics> {
+    private MetricsValueMatcher value;
+
+    @Override
+    public void verify(Metrics metrics) {
+        assertThat(metrics.getValues()).isNotEmpty();
+        assertThat(metrics.getValues()).areAtLeastOne(new Condition<MetricsValue>(){
+            @Override
+            public boolean matches(MetricsValue value) {
+                try {
+                    AtLeastOneOfMetricsMatcher.this.getValue().verify(value);
+                    return true;
+                } catch (Throwable t) {
+                    return false;
+                }
+            }
+        });
+    }
+
+    public MetricsValueMatcher getValue() {
+        return value;
+    }
+
+    public void setValue(MetricsValueMatcher value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "OneOfMetricsMatcher{" +
+            "value=" + value +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/Metrics.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/Metrics.java
new file mode 100644
index 0000000..b0cf0e3
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/Metrics.java
@@ -0,0 +1,44 @@
+/*
+ * 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.skywalking.e2e.metrics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Metrics {
+    private List<MetricsValue> values = new ArrayList<>();
+
+    public List<MetricsValue> getValues() {
+        return values;
+    }
+
+    public void setValues(List<MetricsValue> values) {
+        this.values = values;
+    }
+
+    @Override
+    public String toString() {
+        return "Metrics{" +
+            "values=" + values +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
new file mode 100644
index 0000000..a78f50f
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsData.java
@@ -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.skywalking.e2e.metrics;
+
+/**
+ * @author kezhenxu94
+ */
+public class MetricsData {
+    private Metrics metrics;
+
+    public Metrics getMetrics() {
+        return metrics;
+    }
+
+    public void setMetrics(Metrics metrics) {
+        this.metrics = metrics;
+    }
+
+    @Override
+    public String toString() {
+        return "MetricsData{" +
+            "metrics=" + metrics +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
new file mode 100644
index 0000000..edcbce1
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java
@@ -0,0 +1,71 @@
+/*
+ * 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.skywalking.e2e.metrics;
+
+import org.apache.skywalking.e2e.AbstractQuery;
+
+/**
+ * @author kezhenxu94
+ */
+public class MetricsQuery extends AbstractQuery<MetricsQuery> {
+    public static String SERVICE_P99 = "service_p99";
+    public static String SERVICE_P95 = "service_p95";
+    public static String SERVICE_P90 = "service_p90";
+    public static String SERVICE_P75 = "service_p75";
+    public static String SERVICE_P50 = "service_p50";
+
+    public static String ENDPOINT_P99 = "endpoint_p99";
+    public static String ENDPOINT_P95 = "endpoint_p95";
+    public static String ENDPOINT_P90 = "endpoint_p90";
+    public static String ENDPOINT_P75 = "endpoint_p75";
+    public static String ENDPOINT_P50 = "endpoint_p50";
+
+    public static String SERVICE_INSTANCE_RESP_TIME = "service_instance_resp_time";
+    public static String SERVICE_INSTANCE_CPM = "service_instance_cpm";
+    public static String SERVICE_INSTANCE_SLA = "service_instance_sla";
+
+    private String id;
+    private String metricsName;
+
+    public String id() {
+        return id;
+    }
+
+    public MetricsQuery id(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public String metricsName() {
+        return metricsName;
+    }
+
+    public MetricsQuery metricsName(String metricsName) {
+        this.metricsName = metricsName;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "MetricsQuery{" +
+            "id='" + id + '\'' +
+            ", metricsName='" + metricsName + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValue.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValue.java
new file mode 100644
index 0000000..6b27ce9
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValue.java
@@ -0,0 +1,42 @@
+/*
+ * 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.skywalking.e2e.metrics;
+
+/**
+ * @author kezhenxu94
+ */
+public class MetricsValue {
+    private String value;
+
+    public String getValue() {
+        return value;
+    }
+
+    public MetricsValue setValue(String value) {
+        this.value = value;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "MetricsValue{" +
+            "value='" + value + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValueMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValueMatcher.java
new file mode 100644
index 0000000..f3e70f3
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsValueMatcher.java
@@ -0,0 +1,52 @@
+/*
+ * 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.skywalking.e2e.metrics;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.Objects;
+
+/**
+ * @author kezhenxu94
+ */
+public class MetricsValueMatcher extends AbstractMatcher<MetricsValue> {
+    private String value;
+
+    @Override
+    public void verify(MetricsValue metricsValue) {
+        if (Objects.nonNull(getValue())) {
+            doVerify(getValue(), metricsValue.getValue());
+        }
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "MetricsValueMatcher{" +
+            "value='" + value + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/Service.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/Service.java
new file mode 100644
index 0000000..3df175c
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/Service.java
@@ -0,0 +1,53 @@
+/*
+ * 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.skywalking.e2e.service;
+
+/**
+ * @author kezhenxu94
+ */
+public class Service {
+    private String key;
+    private String label;
+
+    public String getKey() {
+        return key;
+    }
+
+    public Service setKey(String key) {
+        this.key = key;
+        return this;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public Service setLabel(String label) {
+        this.label = label;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "Service{" +
+            "key='" + key + '\'' +
+            ", label='" + label + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServiceMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServiceMatcher.java
new file mode 100644
index 0000000..00fcdc6
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServiceMatcher.java
@@ -0,0 +1,75 @@
+/*
+ * 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.skywalking.e2e.service;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.Objects;
+
+/**
+ * A simple matcher to verify the given {@code Service} is expected
+ *
+ * @author kezhenxu94
+ */
+public class ServiceMatcher extends AbstractMatcher<Service> {
+
+    private String key;
+    private String label;
+
+    @Override
+    public void verify(final Service service) {
+        if (Objects.nonNull(getKey())) {
+            verifyKey(service);
+        }
+
+        if (Objects.nonNull(getLabel())) {
+            verifyLabel(service);
+        }
+    }
+
+    private void verifyKey(Service service) {
+        final String expected = this.getKey();
+        final String actual = service.getKey();
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyLabel(Service service) {
+        final String expected = this.getLabel();
+        final String actual = String.valueOf(service.getLabel());
+
+        doVerify(expected, actual);
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesData.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesData.java
new file mode 100644
index 0000000..c14a9d2
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesData.java
@@ -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.skywalking.e2e.service;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class ServicesData {
+    private List<Service> services;
+
+    public List<Service> getServices() {
+      return services;
+    }
+
+    public void setServices(List<Service> services) {
+      this.services = services;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesMatcher.java
new file mode 100644
index 0000000..8b33d7f
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesMatcher.java
@@ -0,0 +1,53 @@
+/*
+ * 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.skywalking.e2e.service;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public class ServicesMatcher {
+    private List<ServiceMatcher> services;
+
+    public ServicesMatcher() {
+        this.services = new LinkedList<>();
+    }
+
+    public List<ServiceMatcher> getServices() {
+        return services;
+    }
+
+    public void setServices(List<ServiceMatcher> services) {
+        this.services = services;
+    }
+
+    public void verify(final List<Service> services) {
+        assertThat(services).hasSameSizeAs(this.getServices());
+
+        int size = this.getServices().size();
+
+        for (int i = 0; i < size; i++) {
+            this.getServices().get(i).verify(services.get(i));
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesQuery.java
new file mode 100644
index 0000000..d1400f4
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/ServicesQuery.java
@@ -0,0 +1,27 @@
+/*
+ * 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.skywalking.e2e.service;
+
+import org.apache.skywalking.e2e.AbstractQuery;
+
+/**
+ * @author kezhenxu94
+ */
+public class ServicesQuery extends AbstractQuery<ServicesQuery> {
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoint.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoint.java
new file mode 100644
index 0000000..b23f450
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoint.java
@@ -0,0 +1,51 @@
+/*
+ * 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.skywalking.e2e.service.endpoint;
+
+/**
+ * @author kezhenxu94
+ */
+public class Endpoint {
+    private String key;
+    private String label;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public String toString() {
+        return "Endpoint{" +
+            "key='" + key + '\'' +
+            ", label='" + label + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointMatcher.java
new file mode 100644
index 0000000..f35da29
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointMatcher.java
@@ -0,0 +1,57 @@
+/*
+ * 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.skywalking.e2e.service.endpoint;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.Objects;
+
+/**
+ * @author kezhenxu94
+ */
+public class EndpointMatcher extends AbstractMatcher<Endpoint> {
+    private String key;
+    private String label;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    @Override
+    public void verify(final Endpoint endpoint) {
+        if (Objects.nonNull(getKey())) {
+            doVerify(getKey(), endpoint.getKey());
+        }
+        if (Objects.nonNull(getLabel())) {
+            doVerify(getLabel(), endpoint.getLabel());
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointQuery.java
new file mode 100644
index 0000000..d6be61d
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointQuery.java
@@ -0,0 +1,53 @@
+/*
+ * 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.skywalking.e2e.service.endpoint;
+
+/**
+ * @author kezhenxu94
+ */
+public class EndpointQuery {
+    private String serviceId;
+    private String keyword;
+
+    public String serviceId() {
+        return serviceId;
+    }
+
+    public EndpointQuery serviceId(String serviceId) {
+        this.serviceId = serviceId;
+        return this;
+    }
+
+    public String keyword() {
+        return keyword;
+    }
+
+    public EndpointQuery keyword(String keyword) {
+        this.keyword = keyword;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "EndpointQuery{" +
+            "serviceId='" + serviceId + '\'' +
+            ", keyword='" + keyword + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoints.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoints.java
new file mode 100644
index 0000000..cd89860
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/Endpoints.java
@@ -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.skywalking.e2e.service.endpoint;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Endpoints {
+    private List<Endpoint> endpoints;
+
+    public List<Endpoint> getEndpoints() {
+        return endpoints;
+    }
+
+    public void setEndpoints(List<Endpoint> endpoints) {
+        this.endpoints = endpoints;
+    }
+
+    @Override
+    public String toString() {
+        return "Endpoints{" +
+            "endpoints=" + endpoints +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointsMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointsMatcher.java
new file mode 100644
index 0000000..89c980b
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/endpoint/EndpointsMatcher.java
@@ -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.skywalking.e2e.service.endpoint;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public class EndpointsMatcher extends AbstractMatcher<Endpoints> {
+    private List<EndpointMatcher> endpoints;
+
+    public List<EndpointMatcher> getEndpoints() {
+        return endpoints;
+    }
+
+    public void setEndpoints(List<EndpointMatcher> endpoints) {
+        this.endpoints = endpoints;
+    }
+
+    @Override
+    public void verify(Endpoints endpoints) {
+        if (Objects.nonNull(getEndpoints())) {
+            assertThat(endpoints.getEndpoints()).hasSameSizeAs(getEndpoints());
+
+            int size = getEndpoints().size();
+
+            for (int i = 0; i < size; i++) {
+                getEndpoints().get(i).verify(endpoints.getEndpoints().get(i));
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "EndpointsMatcher{" +
+            "endpoints=" + endpoints +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Attribute.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Attribute.java
new file mode 100644
index 0000000..c81b3b6
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Attribute.java
@@ -0,0 +1,51 @@
+/*
+ * 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.skywalking.e2e.service.instance;
+
+/**
+ * @author kezhenxu94
+ */
+public class Attribute {
+    private String name;
+    private String value;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "Attribute{" +
+            "name='" + name + '\'' +
+            ", value='" + value + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/AttributeMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/AttributeMatcher.java
new file mode 100644
index 0000000..2293389
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/AttributeMatcher.java
@@ -0,0 +1,63 @@
+/*
+ * 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.skywalking.e2e.service.instance;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.Objects;
+
+/**
+ * @author kezhenxu94
+ */
+public class AttributeMatcher extends AbstractMatcher<Attribute> {
+    private String name;
+    private String value;
+
+    @Override
+    public void verify(final Attribute attribute) {
+        if (Objects.nonNull(attribute.getName())) {
+            doVerify(getName(), attribute.getName());
+            doVerify(getValue(), attribute.getValue());
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "Attribute{" +
+            "name='" + name + '\'' +
+            ", value='" + value + '\'' +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instance.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instance.java
new file mode 100644
index 0000000..57e6a56
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instance.java
@@ -0,0 +1,65 @@
+/*
+ * 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.skywalking.e2e.service.instance;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Instance {
+    private String key;
+    private String label;
+    private List<Attribute> attributes;
+
+    public String getKey() {
+        return key;
+    }
+
+    public Instance setKey(String key) {
+        this.key = key;
+        return this;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public Instance setLabel(String label) {
+        this.label = label;
+        return this;
+    }
+
+    public List<Attribute> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(List<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    @Override
+    public String toString() {
+        return "Instance{" +
+            "key='" + key + '\'' +
+            ", label='" + label + '\'' +
+            ", attributes=" + attributes +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstanceMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstanceMatcher.java
new file mode 100644
index 0000000..af6434a
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstanceMatcher.java
@@ -0,0 +1,113 @@
+/*
+ * 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.skywalking.e2e.service.instance;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A simple matcher to verify the given {@code Service} is expected
+ *
+ * @author kezhenxu94
+ */
+public class InstanceMatcher extends AbstractMatcher<Instance> {
+
+    private String key;
+    private String label;
+    private List<AttributeMatcher> attributes;
+
+    @Override
+    public void verify(final Instance instance) {
+        if (Objects.nonNull(getKey())) {
+            verifyKey(instance);
+        }
+
+        if (Objects.nonNull(getLabel())) {
+            verifyLabel(instance);
+        }
+
+        if (Objects.nonNull(getAttributes())) {
+            verifyAttributes(instance);
+        }
+    }
+
+    private void verifyKey(Instance instance) {
+        final String expected = this.getKey();
+        final String actual = instance.getKey();
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyLabel(Instance instance) {
+        final String expected = this.getLabel();
+        final String actual = String.valueOf(instance.getLabel());
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyAttributes(Instance instance) {
+        final List<AttributeMatcher> expected = this.getAttributes();
+        final List<Attribute> actual = instance.getAttributes();
+
+        assertThat(actual).hasSameSizeAs(expected);
+
+        int size = expected.size();
+
+        for (int i = 0; i < size; i++) {
+            expected.get(i).verify(actual.get(i));
+        }
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public List<AttributeMatcher> getAttributes() {
+        return attributes;
+    }
+
+    public void setAttributes(List<AttributeMatcher> attributes) {
+        this.attributes = attributes;
+    }
+
+    @Override
+    public String toString() {
+        return "InstanceMatcher{" +
+            "key='" + key + '\'' +
+            ", label='" + label + '\'' +
+            ", attributes=" + attributes +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instances.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instances.java
new file mode 100644
index 0000000..6896a03
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/Instances.java
@@ -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.skywalking.e2e.service.instance;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Instances {
+    private List<Instance> instances;
+
+    public List<Instance> getInstances() {
+        return instances;
+    }
+
+    public void setInstances(List<Instance> instances) {
+        this.instances = instances;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesMatcher.java
new file mode 100644
index 0000000..c367958
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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.skywalking.e2e.service.instance;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+import org.assertj.core.api.Assertions;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class InstancesMatcher extends AbstractMatcher<Instances> {
+    private List<InstanceMatcher> instances;
+
+    public InstancesMatcher() {
+        this.instances = new LinkedList<>();
+    }
+
+    public List<InstanceMatcher> getInstances() {
+        return instances;
+    }
+
+    public void setInstances(List<InstanceMatcher> instances) {
+        this.instances = instances;
+    }
+
+    @Override
+    public void verify(final Instances instances) {
+        Assertions.assertThat(instances.getInstances()).hasSameSizeAs(this.getInstances());
+
+        int size = this.getInstances().size();
+
+        for (int i = 0; i < size; i++) {
+            this.getInstances().get(i).verify(instances.getInstances().get(i));
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesQuery.java
new file mode 100644
index 0000000..59878f9
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/service/instance/InstancesQuery.java
@@ -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.skywalking.e2e.service.instance;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * @author kezhenxu94
+ */
+public class InstancesQuery {
+    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmmss");
+
+    private String start = LocalDateTime.now(ZoneOffset.UTC).format(TIME_FORMATTER);
+    private String end = LocalDateTime.now(ZoneOffset.UTC).minusMinutes(15).format(TIME_FORMATTER);
+    private String step = "SECOND";
+    private String serviceId;
+
+    public String serviceId() {
+        return serviceId;
+    }
+
+    public InstancesQuery serviceId(String serviceId) {
+        this.serviceId = serviceId;
+        return this;
+    }
+
+    public String start() {
+        return start;
+    }
+
+    public InstancesQuery start(String start) {
+        this.start = start;
+        return this;
+    }
+
+    public InstancesQuery start(LocalDateTime start) {
+        this.start = start.format(TIME_FORMATTER);
+        return this;
+    }
+
+    public String end() {
+        return end;
+    }
+
+    public InstancesQuery end(String end) {
+        this.end = end;
+        return this;
+    }
+
+    public InstancesQuery end(LocalDateTime end) {
+        this.end = end.format(TIME_FORMATTER);
+        return this;
+    }
+
+    public String step() {
+        return step;
+    }
+
+    public InstancesQuery step(String step) {
+        this.step = step;
+        return this; }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Call.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Call.java
new file mode 100644
index 0000000..ef39714
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Call.java
@@ -0,0 +1,67 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Call {
+    private String id;
+    private String source;
+    private List<String> detectPoints;
+    private String target;
+
+    public String getId() {
+        return id;
+    }
+
+    public Call setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public Call setSource(String source) {
+        this.source = source;
+        return this;
+    }
+
+    public List<String> getDetectPoints() {
+        return detectPoints;
+    }
+
+    public Call setDetectPoints(List<String> detectPoints) {
+        this.detectPoints = detectPoints;
+        return this;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public Call setTarget(String target) {
+        this.target = target;
+        return this;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/CallMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/CallMatcher.java
new file mode 100644
index 0000000..e69b0eb
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/CallMatcher.java
@@ -0,0 +1,104 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public class CallMatcher extends AbstractMatcher<Call> {
+    private String id;
+    private String source;
+    private List<String> detectPoints;
+    private String target;
+
+    @Override
+    public void verify(final Call call) {
+        if (Objects.nonNull(getId())) {
+            final String expected = this.getId();
+            final String actual = call.getId();
+
+            doVerify(expected, actual);
+        }
+
+        if (Objects.nonNull(getSource())) {
+            final String expected = this.getSource();
+            final String actual = call.getSource();
+
+            doVerify(expected, actual);
+        }
+
+        if (Objects.nonNull(getDetectPoints())) {
+            assertThat(getDetectPoints()).hasSameSizeAs(call.getDetectPoints());
+            int size = getDetectPoints().size();
+
+            for (int i = 0; i < size; i++) {
+                final String expected = getDetectPoints().get(i);
+                final String actual = call.getDetectPoints().get(i);
+
+                doVerify(expected, actual);
+            }
+        }
+
+        if (Objects.nonNull(getTarget())) {
+            final String expected = this.getTarget();
+            final String actual = call.getTarget();
+
+            doVerify(expected, actual);
+        }
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public List<String> getDetectPoints() {
+        return detectPoints;
+    }
+
+    public void setDetectPoints(List<String> detectPoints) {
+        this.detectPoints = detectPoints;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Node.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Node.java
new file mode 100644
index 0000000..bb796af
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/Node.java
@@ -0,0 +1,65 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+/**
+ * @author kezhenxu94
+ */
+public class Node {
+    private String id;
+    private String name;
+    private String type;
+    private String isReal;
+
+    public String getId() {
+        return id;
+    }
+
+    public Node setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Node setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public Node setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getReal() {
+        return isReal;
+    }
+
+    public Node setIsReal(String real) {
+        isReal = real;
+        return this;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/NodeMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/NodeMatcher.java
new file mode 100644
index 0000000..b4f264b
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/NodeMatcher.java
@@ -0,0 +1,96 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.Objects;
+
+/**
+ * @author kezhenxu94
+ */
+public class NodeMatcher extends AbstractMatcher<Node> {
+    private String id;
+    private String name;
+    private String type;
+    private String isReal;
+
+    @Override
+    public void verify(final Node node) {
+        if (Objects.nonNull(getId())) {
+            final String expected = this.getId();
+            final String actual = node.getId();
+
+            doVerify(expected, actual);
+        }
+
+        if (Objects.nonNull(getName())) {
+            final String expected = this.getName();
+            final String actual = node.getName();
+
+            doVerify(expected, actual);
+        }
+
+        if (Objects.nonNull(getType())) {
+            final String expected = this.getType();
+            final String actual = node.getType();
+
+            doVerify(expected, actual);
+        }
+
+        if (Objects.nonNull(getIsReal())) {
+            final String expected = this.getIsReal();
+            final String actual = String.valueOf(node.getReal());
+
+            doVerify(expected, actual);
+        }
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getIsReal() {
+        return isReal;
+    }
+
+    public void setIsReal(String isReal) {
+        this.isReal = isReal;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoData.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoData.java
new file mode 100644
index 0000000..bb6c887
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoData.java
@@ -0,0 +1,53 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class TopoData {
+    private List<Node> nodes;
+    private List<Call> calls;
+
+    public TopoData() {
+        nodes = new ArrayList<>();
+        calls = new ArrayList<>();
+    }
+
+    public List<Node> getNodes() {
+        return nodes;
+    }
+
+    public TopoData setNodes(List<Node> nodes) {
+        this.nodes = nodes;
+        return this;
+    }
+
+    public List<Call> getCalls() {
+        return calls;
+    }
+
+    public TopoData setCalls(List<Call> calls) {
+        this.calls = calls;
+        return this;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoMatcher.java
new file mode 100644
index 0000000..aa64b04
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoMatcher.java
@@ -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.skywalking.e2e.topo;
+
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.List;
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A simple matcher to verify the given {@code Service} is expected
+ *
+ * @author kezhenxu94
+ */
+public class TopoMatcher extends AbstractMatcher<TopoData> {
+
+    private List<NodeMatcher> nodes;
+    private List<CallMatcher> calls;
+
+    @Override
+    public void verify(final TopoData topoData) {
+        if (Objects.nonNull(getNodes())) {
+            verifyNodes(topoData);
+        }
+
+        if (Objects.nonNull(getCalls())) {
+            verifyCalls(topoData);
+        }
+    }
+
+    private void verifyNodes(TopoData topoData) {
+        assertThat(topoData.getNodes()).hasSameSizeAs(getNodes());
+
+        int size = getNodes().size();
+
+        for (int i = 0; i < size; i++) {
+            getNodes().get(i).verify(topoData.getNodes().get(i));
+        }
+    }
+
+    private void verifyCalls(TopoData topoData) {
+        assertThat(topoData.getCalls()).hasSameSizeAs(getCalls());
+
+        int size = getCalls().size();
+
+        for (int i = 0; i < size; i++) {
+            getCalls().get(i).verify(topoData.getCalls().get(i));
+        }
+    }
+
+    public List<NodeMatcher> getNodes() {
+        return nodes;
+    }
+
+    public void setNodes(List<NodeMatcher> nodes) {
+        this.nodes = nodes;
+    }
+
+    public List<CallMatcher> getCalls() {
+        return calls;
+    }
+
+    public void setCalls(List<CallMatcher> calls) {
+        this.calls = calls;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoQuery.java
new file mode 100644
index 0000000..ea318a2
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoQuery.java
@@ -0,0 +1,27 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+import org.apache.skywalking.e2e.AbstractQuery;
+
+/**
+ * @author kezhenxu94
+ */
+public class TopoQuery extends AbstractQuery<TopoQuery> {
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoResponse.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoResponse.java
new file mode 100644
index 0000000..6aadb9d
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/topo/TopoResponse.java
@@ -0,0 +1,34 @@
+/*
+ * 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.skywalking.e2e.topo;
+
+/**
+ * @author kezhenxu94
+ */
+public class TopoResponse {
+    private TopoData topo;
+
+    public TopoData getTopo() {
+        return topo;
+    }
+
+    public void setTopo(TopoData topo) {
+        this.topo = topo;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/Trace.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/Trace.java
new file mode 100644
index 0000000..6296d79
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/Trace.java
@@ -0,0 +1,95 @@
+/*
+ * 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.skywalking.e2e.trace;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class Trace {
+    private String key;
+    private final List<String> endpointNames;
+    private int duration;
+    private String start;
+    private boolean isError;
+    private final List<String> traceIds;
+
+    public Trace() {
+        this.endpointNames = new ArrayList<>();
+        this.traceIds = new ArrayList<>();
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public Trace setKey(String key) {
+        this.key = key;
+        return this;
+    }
+
+    public List<String> getEndpointNames() {
+        return endpointNames;
+    }
+
+    public int getDuration() {
+        return duration;
+    }
+
+    public Trace setDuration(int duration) {
+        this.duration = duration;
+        return this;
+    }
+
+    public String getStart() {
+        return start;
+    }
+
+    public Trace setStart(String start) {
+        this.start = start;
+        return this;
+    }
+
+    public boolean isError() {
+        return isError;
+    }
+
+    public Trace setError(boolean error) {
+        isError = error;
+        return this;
+    }
+
+    public List<String> getTraceIds() {
+        return traceIds;
+    }
+
+    @Override
+    public String toString() {
+        return "Trace{" +
+            "key='" + key + '\'' +
+            ", endpointNames=" + endpointNames +
+            ", duration=" + duration +
+            ", start='" + start + '\'' +
+            ", isError=" + isError +
+            ", traceIds=" + traceIds +
+            '}';
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TraceMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TraceMatcher.java
new file mode 100644
index 0000000..30a537e
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TraceMatcher.java
@@ -0,0 +1,171 @@
+/*
+ * 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.skywalking.e2e.trace;
+
+import com.google.common.base.Strings;
+import org.apache.skywalking.e2e.verification.AbstractMatcher;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * A simple matcher to verify the given {@code Trace} is expected
+ *
+ * @author kezhenxu94
+ */
+public class TraceMatcher extends AbstractMatcher<Trace> {
+    private String key;
+    private List<String> endpointNames;
+    private String duration;
+    private String start;
+    private String isError;
+    private List<String> traceIds;
+
+    @Override
+    public void verify(final Trace trace) {
+        if (Objects.nonNull(getKey())) {
+            verifyKey(trace);
+        }
+
+        if (Objects.nonNull(getEndpointNames())) {
+            verifyEndpointName(trace);
+        }
+
+        if (Objects.nonNull(getDuration())) {
+            verifyDuration(trace);
+        }
+
+        if (Objects.nonNull(getStart())) {
+            verifyStart(trace);
+        }
+
+        if (Objects.nonNull(getIsError())) {
+            verifyIsError(trace);
+        }
+
+        if (Objects.nonNull(getTraceIds())) {
+            verifyTraceIds(trace);
+        }
+    }
+
+    private void verifyKey(Trace trace) {
+        final String expected = this.getKey();
+        final String actual = trace.getKey();
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyEndpointName(Trace trace) {
+        assertThat(trace.getEndpointNames()).hasSameSizeAs(getEndpointNames());
+
+        int size = getEndpointNames().size();
+
+        for (int i = 0; i < size; i++) {
+            final String expected = getEndpointNames().get(i);
+            final String actual = Strings.nullToEmpty(trace.getEndpointNames().get(i));
+
+            doVerify(expected, actual);
+        }
+    }
+
+    private void verifyDuration(Trace trace) {
+        final String expected = this.getDuration();
+        final String actual = String.valueOf(trace.getDuration());
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyStart(Trace trace) {
+        final String expected = this.getStart();
+        final String actual = trace.getStart();
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyIsError(Trace trace) {
+        final String expected = this.getIsError();
+        final String actual = Strings.nullToEmpty(String.valueOf(trace.isError()));
+
+        doVerify(expected, actual);
+    }
+
+    private void verifyTraceIds(Trace trace) {
+        assertThat(trace.getTraceIds()).hasSameSizeAs(getTraceIds());
+
+        int size = getTraceIds().size();
+
+        for (int i = 0; i < size; i++) {
+            final String expected = getTraceIds().get(i);
+            final String actual = trace.getTraceIds().get(i);
+
+            doVerify(expected, actual);
+        }
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public void setEndpointNames(List<String> endpointNames) {
+        this.endpointNames = endpointNames;
+    }
+
+    public List<String> getEndpointNames() {
+        return endpointNames != null ? endpointNames : new ArrayList<>();
+    }
+
+    public String getDuration() {
+        return duration;
+    }
+
+    public void setDuration(String duration) {
+        this.duration = duration;
+    }
+
+    public String getStart() {
+        return start;
+    }
+
+    public void setStart(String start) {
+        this.start = start;
+    }
+
+    public String getIsError() {
+        return isError;
+    }
+
+    public void setIsError(String isError) {
+        this.isError = isError;
+    }
+
+    public void setTraceIds(List<String> traceIds) {
+        this.traceIds = traceIds;
+    }
+
+    public List<String> getTraceIds() {
+        return traceIds != null ? traceIds : new ArrayList<>();
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesData.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesData.java
new file mode 100644
index 0000000..aa5b49a
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesData.java
@@ -0,0 +1,49 @@
+/*
+ * 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.skywalking.e2e.trace;
+
+import java.util.List;
+
+/**
+ * @author kezhenxu94
+ */
+public class TracesData {
+  public static class Traces {
+    private List<Trace> data;
+
+    public List<Trace> getData() {
+      return data;
+    }
+
+    public Traces setData(List<Trace> data) {
+      this.data = data;
+      return this;
+    }
+  }
+
+  private Traces traces;
+
+  public Traces getTraces() {
+    return traces;
+  }
+
+  public void setTraces(final Traces traces) {
+    this.traces = traces;
+  }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesMatcher.java
new file mode 100644
index 0000000..cade9cc
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesMatcher.java
@@ -0,0 +1,53 @@
+/*
+ * 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.skywalking.e2e.trace;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public class TracesMatcher {
+    private List<TraceMatcher> traces;
+
+    public TracesMatcher() {
+        this.traces = new LinkedList<>();
+    }
+
+    public List<TraceMatcher> getTraces() {
+        return traces;
+    }
+
+    public void setTraces(List<TraceMatcher> traces) {
+        this.traces = traces;
+    }
+
+    public void verify(final List<Trace> traces) {
+        assertThat(traces).hasSameSizeAs(this.traces);
+
+        int size = this.traces.size();
+
+        for (int i = 0; i < size; i++) {
+            this.traces.get(i).verify(traces.get(i));
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesQuery.java
new file mode 100644
index 0000000..22b2455
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/trace/TracesQuery.java
@@ -0,0 +1,92 @@
+/*
+ * 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.skywalking.e2e.trace;
+
+import org.apache.skywalking.e2e.AbstractQuery;
+
+/**
+ * @author kezhenxu94
+ */
+public class TracesQuery extends AbstractQuery<TracesQuery> {
+    private String traceState = "ALL";
+    private String pageNum = "1";
+    private String pageSize = "15";
+    private String needTotal = "true";
+    private String queryOrder = "BY_DURATION";
+
+    public String traceState() {
+        return traceState;
+    }
+
+    public TracesQuery traceState(String traceState) {
+        this.traceState = traceState;
+        return this;
+    }
+
+    public String pageNum() {
+        return pageNum;
+    }
+
+    public TracesQuery pageNum(String pageNum) {
+        this.pageNum = pageNum;
+        return this;
+    }
+
+    public TracesQuery pageNum(int pageNum) {
+        this.pageNum = String.valueOf(pageNum);
+        return this;
+    }
+
+    public String pageSize() {
+        return pageSize;
+    }
+
+    public TracesQuery pageSize(String pageSize) {
+        this.pageSize = pageSize;
+        return this;
+    }
+
+    public String needTotal() {
+        return needTotal;
+    }
+
+    public TracesQuery needTotal(boolean needTotal) {
+        this.needTotal = String.valueOf(needTotal);
+        return this;
+    }
+
+    public String queryOrder() {
+        return queryOrder;
+    }
+
+    public TracesQuery queryOrder(String queryOrder) {
+        this.queryOrder = queryOrder;
+        return this;
+    }
+
+    public TracesQuery orderByDuration() {
+        this.queryOrder = "BY_DURATION";
+        return this;
+    }
+
+    public TracesQuery pageSize(int pageSize) {
+        this.pageSize = String.valueOf(pageSize);
+        return this;
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/verification/AbstractMatcher.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/verification/AbstractMatcher.java
new file mode 100644
index 0000000..a47f5f5
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/verification/AbstractMatcher.java
@@ -0,0 +1,71 @@
+/*
+ * 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.skywalking.e2e.verification;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+public abstract class AbstractMatcher<T> {
+    private static final Pattern NE_MATCHER = Pattern.compile("ne\\s+(?<val>.+)");
+    private static final Pattern GT_MATCHER = Pattern.compile("gt\\s+(?<val>.+)");
+    private static final Pattern GE_MATCHER = Pattern.compile("ge\\s+(?<val>.+)");
+    private static final Pattern NN_MATCHER = Pattern.compile("^not null$");
+
+    public abstract void verify(T t);
+
+    protected void doVerify(String expected, String actual) {
+        Matcher matcher = NN_MATCHER.matcher(expected);
+        if (matcher.find()) {
+            assertThat(actual).isNotNull();
+            return;
+        }
+
+        matcher = NE_MATCHER.matcher(expected);
+        if (matcher.find()) {
+            assertThat(actual).isNotEqualTo(matcher.group("val"));
+            return;
+        }
+
+        matcher = GT_MATCHER.matcher(expected);
+        if (matcher.find()) {
+            String val = matcher.group("val");
+
+            assertThat(val).isNotBlank();
+            assertThat(Double.parseDouble(actual)).isGreaterThan(Double.parseDouble(val));
+            return;
+        }
+
+        matcher = GE_MATCHER.matcher(expected);
+        if (matcher.find()) {
+            String val = matcher.group("val");
+
+            assertThat(val).isNotBlank();
+            assertThat(Double.parseDouble(actual)).isGreaterThanOrEqualTo(Double.parseDouble(val));
+            return;
+        }
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+}
diff --git a/test/e2e/e2e-base/src/main/resources/endpoints.gql b/test/e2e/e2e-base/src/main/resources/endpoints.gql
new file mode 100644
index 0000000..c35cd32
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/endpoints.gql
@@ -0,0 +1,28 @@
+# 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.
+
+{
+  "query":"query queryEndpoints($serviceId: ID!, $keyword: String!) {
+    endpoints: searchEndpoint(serviceId: $serviceId, keyword: $keyword, limit: 100) {
+      key: id
+      label: name
+    }
+  }",
+  "variables": {
+    "serviceId": "{serviceId}",
+    "keyword": ""
+  }
+}
diff --git a/test/e2e/e2e-base/src/main/resources/instances.gql b/test/e2e/e2e-base/src/main/resources/instances.gql
new file mode 100644
index 0000000..949c793
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/instances.gql
@@ -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.
+
+{
+  "query":"query queryInstances($serviceId: ID!, $duration: Duration!) {
+    instances: getServiceInstances(duration: $duration, serviceId: $serviceId) {
+      key: id
+      label: name
+      attributes {
+        name
+        value
+      }
+    }
+  }",
+  "variables": {
+    "serviceId": "{serviceId}",
+    "duration": {
+      "start": "{start}",
+      "end": "{end}",
+      "step": "{step}"
+    }
+  }
+}
diff --git a/test/e2e/e2e-base/src/main/resources/metrics.gql b/test/e2e/e2e-base/src/main/resources/metrics.gql
new file mode 100644
index 0000000..38f7253
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/metrics.gql
@@ -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.
+
+{
+  "query":"query ($id: ID!, $duration: Duration!) {
+    metrics: getLinearIntValues(metric: {
+      name: \"{metricsName}\"
+      id: $id
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+  }",
+  "variables": {
+    "duration": {
+      "start": "{start}",
+      "end": "{end}",
+      "step": "{step}"
+    },
+    "id": "{id}"
+  }
+}
diff --git a/test/e2e/e2e-base/src/main/resources/services.gql b/test/e2e/e2e-base/src/main/resources/services.gql
new file mode 100644
index 0000000..1855d6e
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/services.gql
@@ -0,0 +1,31 @@
+# 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.
+
+{
+    "query": "query queryServices($duration: Duration!) {
+        services: getAllServices(duration: $duration) {
+          key: id
+          label: name
+        }
+    }",
+    "variables": {
+        "duration": {
+            "start": "{start}",
+            "end": "{end}",
+            "step": "{step}"
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/resources/topo.gql b/test/e2e/e2e-base/src/main/resources/topo.gql
new file mode 100644
index 0000000..94febab
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/topo.gql
@@ -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.
+
+{
+    "query": "query queryTopo($duration: Duration!) {
+        topo: getGlobalTopology(duration: $duration) {
+            nodes {
+                id
+                name
+                type
+                isReal
+            }
+            calls {
+                id
+                source
+                detectPoints
+                target
+            }
+        }
+    }",
+    "variables": {
+        "duration": {
+            "start": "{start}",
+            "end": "{end}",
+            "step": "{step}"
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/main/resources/traces.gql b/test/e2e/e2e-base/src/main/resources/traces.gql
new file mode 100644
index 0000000..a573f57
--- /dev/null
+++ b/test/e2e/e2e-base/src/main/resources/traces.gql
@@ -0,0 +1,42 @@
+# 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.
+
+{
+    "query": "query queryTraces($condition: TraceQueryCondition) {
+        traces: queryBasicTraces(condition: $condition) {
+            data: traces {
+                key: segmentId endpointNames duration start isError traceIds
+            }
+            total
+        }
+    }",
+    "variables": {
+        "condition": {
+            "queryDuration": {
+                "start": "{start}",
+                "end": "{end}",
+                "step": "{step}"
+            },
+            "traceState": "{traceState}",
+            "paging": {
+                "pageNum": {pageNum},
+                "pageSize": {pageSize},
+                "needTotal": {needTotal}
+            },
+            "queryOrder": "{queryOrder}"
+        }
+    }
+}
diff --git a/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMatcher.java b/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMatcher.java
new file mode 100644
index 0000000..44e5f69
--- /dev/null
+++ b/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMatcher.java
@@ -0,0 +1,92 @@
+/*
+ * 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.skywalking.e2e;
+
+import org.apache.skywalking.e2e.trace.Trace;
+import org.apache.skywalking.e2e.trace.TraceMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author kezhenxu94
+ */
+public class TestMatcher {
+    private InputStream expectedInputStream;
+    private TraceMatcher traceMatcher;
+
+    @Before
+    public void setUp() throws IOException {
+        expectedInputStream = new ClassPathResource("test.yml").getInputStream();
+        traceMatcher = new Yaml().loadAs(expectedInputStream, TraceMatcher.class);
+    }
+
+    @Test
+    public void shouldSuccess()  {
+        final Trace trace = new Trace()
+            .setKey("abc")
+            .setStart("1")
+            .setError(false);
+        trace.getEndpointNames().add("e2e/test");
+        trace.getTraceIds().add("id1");
+        trace.getTraceIds().add("id2");
+        traceMatcher.verify(trace);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void shouldVerifyNotNull() {
+        final Trace trace = new Trace()
+            .setStart("1")
+            .setError(false);
+        trace.getEndpointNames().add("e2e/test");
+        trace.getTraceIds().add("id1");
+        trace.getTraceIds().add("id2");
+        traceMatcher.verify(trace);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void shouldVerifyGreaterOrEqualTo() {
+        final Trace trace = new Trace()
+            .setKey("abc")
+            .setDuration(-1)
+            .setStart("1")
+            .setError(false);
+        trace.getEndpointNames().add("e2e/test");
+        trace.getTraceIds().add("id1");
+        trace.getTraceIds().add("id2");
+        traceMatcher.verify(trace);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void shouldVerifyGreaterThan() {
+        final Trace trace = new Trace()
+            .setKey("abc")
+            .setDuration(1)
+            .setStart("0")
+            .setError(false);
+        trace.getEndpointNames().add("e2e/test");
+        trace.getTraceIds().add("id1");
+        trace.getTraceIds().add("id2");
+        traceMatcher.verify(trace);
+    }
+}
diff --git a/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMetricsMatcher.java b/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMetricsMatcher.java
new file mode 100644
index 0000000..096032d
--- /dev/null
+++ b/test/e2e/e2e-base/src/test/java/org/apache/skywalking/e2e/TestMetricsMatcher.java
@@ -0,0 +1,52 @@
+/*
+ * 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.skywalking.e2e;
+
+import org.apache.skywalking.e2e.metrics.AtLeastOneOfMetricsMatcher;
+import org.apache.skywalking.e2e.metrics.Metrics;
+import org.apache.skywalking.e2e.metrics.MetricsValue;
+import org.apache.skywalking.e2e.metrics.MetricsValueMatcher;
+import org.junit.Test;
+
+/**
+ * @author kezhenxu94
+ */
+public class TestMetricsMatcher {
+    @Test
+    public void shouldVerifyOneOf() {
+        Metrics metrics = new Metrics();
+        metrics.getValues().add(new MetricsValue().setValue("12"));
+        AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new AtLeastOneOfMetricsMatcher();
+        MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+        greaterThanZero.setValue("gt 0");
+        instanceRespTimeMatcher.setValue(greaterThanZero);
+        instanceRespTimeMatcher.verify(metrics);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void shouldFailedVerifyOneOf() {
+        Metrics metrics = new Metrics();
+        metrics.getValues().add(new MetricsValue().setValue("0"));
+        AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new AtLeastOneOfMetricsMatcher();
+        MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+        greaterThanZero.setValue("gt 0");
+        instanceRespTimeMatcher.setValue(greaterThanZero);
+        instanceRespTimeMatcher.verify(metrics);
+    }
+}
diff --git a/test/e2e/e2e-base/src/test/resources/test.yml b/test/e2e/e2e-base/src/test/resources/test.yml
new file mode 100644
index 0000000..f5fe51a
--- /dev/null
+++ b/test/e2e/e2e-base/src/test/resources/test.yml
@@ -0,0 +1,31 @@
+# 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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+key: not null
+endpointNames:
+  - e2e/test
+duration: ge 0
+start: gt 0
+isError: false
+traceIds:
+  - id1
+  - id2
diff --git a/test/e2e/e2e-single-service/pom.xml b/test/e2e/e2e-single-service/pom.xml
new file mode 100644
index 0000000..866ef62
--- /dev/null
+++ b/test/e2e/e2e-single-service/pom.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<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">
+    <parent>
+        <artifactId>apache-skywalking-e2e</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>1.0.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>e2e-single-service</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <version>${h2.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>e2e-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring.boot.version}</version>
+                <configuration>
+                    <executable>true</executable>
+                    <addResources>true</addResources>
+                    <excludeDevtools>true</excludeDevtools>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>io.fabric8</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <configuration>
+                    <containerNamePattern>%a-%t</containerNamePattern>
+                    <images>
+                        <image>
+                            <name>skyapm/e2e-container:${e2e.container.version}</name>
+                            <alias>skywalking-e2e-container-${build.id}</alias>
+                            <run>
+                                <env>
+                                    <INSTRUMENTED_SERVICE>${project.build.finalName}.jar</INSTRUMENTED_SERVICE>
+                                </env>
+                                <ports>
+                                    <port>+webapp.host:webapp.port:8080</port>
+                                    <port>+client.host:client.port:9090</port>
+                                </ports>
+                                <volumes>
+                                    <bind>
+                                        <volume>
+                                            ${project.basedir}/../../../dist/apache-skywalking-apm-bin:/skywalking
+                                        </volume>
+                                        <volume>
+                                            ${project.build.directory}:/home
+                                        </volume>
+                                    </bind>
+                                </volumes>
+                                <wait>
+                                    <http>
+                                        <url>
+                                            http://${docker.host.address}:${client.port}/e2e/health-check
+                                        </url>
+                                        <method>GET</method>
+                                        <status>200</status>
+                                    </http>
+                                    <time>300000</time>
+                                </wait>
+                            </run>
+                        </image>
+                    </images>
+                </configuration>
+            </plugin>
+
+            <!-- set the system properties that can be used in test codes -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <configuration>
+                    <systemPropertyVariables>
+                        <sw.webapp.host>
+                            ${webapp.host}
+                        </sw.webapp.host>
+                        <sw.webapp.port>
+                            ${webapp.port}
+                        </sw.webapp.port>
+                        <client.host>
+                            ${client.host}
+                        </client.host>
+                        <client.port>
+                            ${client.port}
+                        </client.port>
+                    </systemPropertyVariables>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/SampleClientApplication.java b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/SampleClientApplication.java
new file mode 100644
index 0000000..a55dbd6
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/SampleClientApplication.java
@@ -0,0 +1,34 @@
+/*
+ * 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.skywalking.e2e.sample.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+/**
+ * @author kezhenxu94
+ */
+@EnableJpaRepositories
+@SpringBootApplication
+public class SampleClientApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(SampleClientApplication.class, args);
+    }
+}
diff --git a/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/TestController.java b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/TestController.java
new file mode 100644
index 0000000..cfbe5a2
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/TestController.java
@@ -0,0 +1,48 @@
+/*
+ * 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.skywalking.e2e.sample.client;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author kezhenxu94
+ */
+@RestController
+@RequestMapping("/e2e")
+public class TestController {
+    private final UserRepo userRepo;
+
+    public TestController(final UserRepo userRepo) {
+        this.userRepo = userRepo;
+    }
+
+    @GetMapping("/health-check")
+    public String hello() {
+        return "healthy";
+    }
+
+    @PostMapping("/users")
+    public User createAuthor(@RequestBody final User user) {
+        return userRepo.save(user);
+    }
+}
diff --git a/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/User.java b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/User.java
new file mode 100644
index 0000000..4589c7f
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/User.java
@@ -0,0 +1,56 @@
+/*
+ * 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.skywalking.e2e.sample.client;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+/**
+ * @author kezhenxu94
+ */
+@Entity
+public class User {
+    public User() {
+    }
+
+    @Id
+    @GeneratedValue
+    private Long id;
+
+    @Column
+    private String name;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(final Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+}
diff --git a/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/UserRepo.java b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/UserRepo.java
new file mode 100644
index 0000000..882af92
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/main/java/org/apache/skywalking/e2e/sample/client/UserRepo.java
@@ -0,0 +1,27 @@
+/*
+ * 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.skywalking.e2e.sample.client;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author kezhenxu94
+ */
+public interface UserRepo extends JpaRepository<User, Long> {
+}
diff --git a/test/e2e/e2e-single-service/src/main/resources/application.yml b/test/e2e/e2e-single-service/src/main/resources/application.yml
new file mode 100644
index 0000000..ef3ed01
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/main/resources/application.yml
@@ -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.
+
+server:
+  port: 9090
+
+spring:
+  main:
+    banner-mode: 'off'
+  datasource:
+    url: jdbc:h2:mem:testdb
+    driver-class-name: org.h2.Driver
+    data-username: sa
+    password: sa
+    platform: org.hibernate.dialect.H2Dialect
+  jpa:
+    generate-ddl: true
+    hibernate:
+      ddl-auto: create-drop
+    properties:
+      hibernate.format_sql: true
+    show-sql: true
diff --git a/test/e2e/e2e-single-service/src/test/java/org/apache/skywalking/e2e/SampleVerificationITCase.java b/test/e2e/e2e-single-service/src/test/java/org/apache/skywalking/e2e/SampleVerificationITCase.java
new file mode 100644
index 0000000..cb04459
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/java/org/apache/skywalking/e2e/SampleVerificationITCase.java
@@ -0,0 +1,292 @@
+/*
+ * 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.skywalking.e2e;
+
+import org.apache.skywalking.e2e.metrics.AtLeastOneOfMetricsMatcher;
+import org.apache.skywalking.e2e.metrics.Metrics;
+import org.apache.skywalking.e2e.metrics.MetricsQuery;
+import org.apache.skywalking.e2e.metrics.MetricsValueMatcher;
+import org.apache.skywalking.e2e.service.Service;
+import org.apache.skywalking.e2e.service.ServicesMatcher;
+import org.apache.skywalking.e2e.service.ServicesQuery;
+import org.apache.skywalking.e2e.service.endpoint.Endpoint;
+import org.apache.skywalking.e2e.service.endpoint.EndpointQuery;
+import org.apache.skywalking.e2e.service.endpoint.Endpoints;
+import org.apache.skywalking.e2e.service.endpoint.EndpointsMatcher;
+import org.apache.skywalking.e2e.service.instance.Instance;
+import org.apache.skywalking.e2e.service.instance.Instances;
+import org.apache.skywalking.e2e.service.instance.InstancesMatcher;
+import org.apache.skywalking.e2e.service.instance.InstancesQuery;
+import org.apache.skywalking.e2e.topo.TopoData;
+import org.apache.skywalking.e2e.topo.TopoMatcher;
+import org.apache.skywalking.e2e.topo.TopoQuery;
+import org.apache.skywalking.e2e.trace.Trace;
+import org.apache.skywalking.e2e.trace.TracesMatcher;
+import org.apache.skywalking.e2e.trace.TracesQuery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.client.RestTemplate;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.ENDPOINT_P50;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.ENDPOINT_P75;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.ENDPOINT_P90;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.ENDPOINT_P95;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.ENDPOINT_P99;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_INSTANCE_CPM;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_INSTANCE_RESP_TIME;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_INSTANCE_SLA;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P50;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P75;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P90;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P95;
+import static org.apache.skywalking.e2e.metrics.MetricsQuery.SERVICE_P99;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author kezhenxu94
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+public class SampleVerificationITCase {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SampleVerificationITCase.class);
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    private SimpleQueryClient queryClient;
+    private String instrumentedServiceUrl;
+
+    @Before
+    public void setUp() {
+        final String swWebappHost = System.getProperty("sw.webapp.host", "127.0.0.1");
+        final String swWebappPort = System.getProperty("sw.webapp.port", "32783");
+        final String instrumentedServiceHost0 = System.getProperty("client.host", "127.0.0.1");
+        final String instrumentedServicePort0 = System.getProperty("client.port", "32782");
+        final String queryClientUrl = "http://" + swWebappHost + ":" + swWebappPort + "/graphql";
+        queryClient = new SimpleQueryClient(queryClientUrl);
+        instrumentedServiceUrl = "http://" + instrumentedServiceHost0 + ":" + instrumentedServicePort0;
+    }
+
+    @Test
+    @DirtiesContext
+    public void verify() throws Exception {
+        final LocalDateTime minutesAgo = LocalDateTime.now(ZoneOffset.UTC);
+
+        final Map<String, String> user = new HashMap<>();
+        user.put("name", "SkyWalking");
+        final ResponseEntity<String> responseEntity = restTemplate.postForEntity(
+            instrumentedServiceUrl + "/e2e/users",
+            user,
+            String.class
+        );
+        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+        Thread.sleep(5000);
+
+        verifyTraces(minutesAgo);
+
+        verifyServices(minutesAgo);
+
+        verifyTopo(minutesAgo);
+    }
+
+    private void verifyTopo(LocalDateTime minutesAgo) throws Exception {
+        final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
+
+        final TopoData topoData = queryClient.topo(
+            new TopoQuery()
+                .step("MINUTE")
+                .start(minutesAgo.minusDays(1))
+                .end(now)
+        );
+
+        InputStream expectedInputStream =
+            new ClassPathResource("expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.topo.yml").getInputStream();
+
+        final TopoMatcher topoMatcher = new Yaml().loadAs(expectedInputStream, TopoMatcher.class);
+        topoMatcher.verify(topoData);
+    }
+
+    private void verifyServices(LocalDateTime minutesAgo) throws Exception {
+        final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
+
+        final List<Service> services = queryClient.services(
+            new ServicesQuery()
+                .start(minutesAgo)
+                .end(now)
+        );
+
+        InputStream expectedInputStream =
+            new ClassPathResource("expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.services.yml").getInputStream();
+
+        final ServicesMatcher servicesMatcher = new Yaml().loadAs(expectedInputStream, ServicesMatcher.class);
+        servicesMatcher.verify(services);
+
+        for (Service service : services) {
+            LOGGER.info("verifying service instances: {}", service);
+
+            verifyServiceMetrics(service);
+
+            Instances instances = verifyServiceInstances(minutesAgo, now, service);
+
+            verifyInstancesMetrics(instances);
+
+            Endpoints endpoints = verifyServiceEndpoints(minutesAgo, now, service);
+
+            verifyEndpointsMetrics(endpoints);
+        }
+    }
+
+    private Instances verifyServiceInstances(LocalDateTime minutesAgo, LocalDateTime now, Service service) throws Exception {
+        InputStream expectedInputStream;
+        Instances instances = queryClient.instances(
+            new InstancesQuery()
+                .serviceId(service.getKey())
+                .start(minutesAgo)
+                .end(now)
+        );
+        expectedInputStream =
+            new ClassPathResource("expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.instances.yml").getInputStream();
+        final InstancesMatcher instancesMatcher = new Yaml().loadAs(expectedInputStream, InstancesMatcher.class);
+        instancesMatcher.verify(instances);
+        return instances;
+    }
+
+    private Endpoints verifyServiceEndpoints(LocalDateTime minutesAgo, LocalDateTime now, Service service) throws Exception {
+        Endpoints instances = queryClient.endpoints(
+            new EndpointQuery().serviceId(service.getKey())
+        );
+        InputStream expectedInputStream =
+            new ClassPathResource("expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.endpoints.yml").getInputStream();
+        final EndpointsMatcher endpointsMatcher = new Yaml().loadAs(expectedInputStream, EndpointsMatcher.class);
+        endpointsMatcher.verify(instances);
+        return instances;
+    }
+
+    private void verifyInstancesMetrics(Instances instances) throws Exception {
+        final String[] instanceMetricsNames = new String[] {
+            SERVICE_INSTANCE_RESP_TIME,
+            SERVICE_INSTANCE_CPM,
+            SERVICE_INSTANCE_SLA
+        };
+        for (Instance instance : instances.getInstances()) {
+            for (String metricsName : instanceMetricsNames) {
+                LOGGER.info("verifying service instance response time: {}", instance);
+                final Metrics instanceRespTime = queryClient.metrics(
+                    new MetricsQuery()
+                        .step("MINUTE")
+                        .metricsName(metricsName)
+                        .id(instance.getKey())
+                );
+                AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new AtLeastOneOfMetricsMatcher();
+                MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+                greaterThanZero.setValue("gt 0");
+                instanceRespTimeMatcher.setValue(greaterThanZero);
+                instanceRespTimeMatcher.verify(instanceRespTime);
+                LOGGER.info("{}: {}", metricsName, instanceRespTime);
+            }
+        }
+    }
+
+    private void verifyEndpointsMetrics(Endpoints endpoints) throws Exception {
+        final String[] endpointMetricsNames = {
+            ENDPOINT_P99,
+            ENDPOINT_P95,
+            ENDPOINT_P90,
+            ENDPOINT_P75,
+            ENDPOINT_P50
+        };
+        for (Endpoint endpoint : endpoints.getEndpoints()) {
+            if (!endpoint.getLabel().equals("/e2e/users")) {
+                continue;
+            }
+            for (String metricName : endpointMetricsNames) {
+                LOGGER.info("verifying endpoint {}, metrics: {}", endpoint, metricName);
+                final Metrics metrics = queryClient.metrics(
+                    new MetricsQuery()
+                        .step("MINUTE")
+                        .metricsName(metricName)
+                        .id(endpoint.getKey())
+                );
+                AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new AtLeastOneOfMetricsMatcher();
+                MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+                greaterThanZero.setValue("gt 0");
+                instanceRespTimeMatcher.setValue(greaterThanZero);
+                instanceRespTimeMatcher.verify(metrics);
+                LOGGER.info("metrics: {}", metrics);
+            }
+        }
+    }
+
+    private void verifyServiceMetrics(Service service) throws Exception {
+        final String[] serviceMetrics = {
+            SERVICE_P99,
+            SERVICE_P95,
+            SERVICE_P90,
+            SERVICE_P75,
+            SERVICE_P50
+        };
+        for (String metricName : serviceMetrics) {
+            LOGGER.info("verifying service {}, metrics: {}", service, metricName);
+            final Metrics instanceRespTime = queryClient.metrics(
+                new MetricsQuery()
+                    .step("MINUTE")
+                    .metricsName(metricName)
+                    .id(service.getKey())
+            );
+            AtLeastOneOfMetricsMatcher instanceRespTimeMatcher = new AtLeastOneOfMetricsMatcher();
+            MetricsValueMatcher greaterThanZero = new MetricsValueMatcher();
+            greaterThanZero.setValue("gt 0");
+            instanceRespTimeMatcher.setValue(greaterThanZero);
+            instanceRespTimeMatcher.verify(instanceRespTime);
+            LOGGER.info("instanceRespTime: {}", instanceRespTime);
+        }
+    }
+
+    private void verifyTraces(LocalDateTime minutesAgo) throws Exception {
+        final LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
+
+        final List<Trace> traces = queryClient.traces(
+            new TracesQuery()
+                .start(minutesAgo)
+                .end(now)
+                .orderByDuration()
+        );
+
+        InputStream expectedInputStream =
+            new ClassPathResource("expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.traces.yml").getInputStream();
+
+        final TracesMatcher tracesMatcher = new Yaml().loadAs(expectedInputStream, TracesMatcher.class);
+        tracesMatcher.verify(traces);
+    }
+}
diff --git a/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.endpoints.yml b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.endpoints.yml
new file mode 100644
index 0000000..a1f5b45
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.endpoints.yml
@@ -0,0 +1,27 @@
+# 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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+endpoints:
+  - key: not null
+    label: /e2e/health-check
+  - key: not null
+    label: /e2e/users
diff --git a/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.instances.yml b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.instances.yml
new file mode 100644
index 0000000..26bb314
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.instances.yml
@@ -0,0 +1,34 @@
+# 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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+instances:
+  - key: 2
+    label: not null
+    attributes:
+      - name: os_name
+        value: not null
+      - name: host_name
+        value: not null
+      - name: process_no
+        value: gt 0
+      - name: ipv4s
+        value: not null
diff --git a/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.services.yml b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.services.yml
new file mode 100644
index 0000000..07ff835
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.services.yml
@@ -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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+services:
+  - key: 2
+    label: "Your_ApplicationName"
diff --git a/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.topo.yml b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.topo.yml
new file mode 100644
index 0000000..3a455f5
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.topo.yml
@@ -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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+nodes:
+  - id: 1
+    name: User
+    type: USER
+    isReal: false
+  - id: 2
+    name: Your_ApplicationName
+    type: Tomcat
+    isReal: true
+  - id: 3
+    name: "localhost:-1"
+    type: H2
+    isReal: false
+calls:
+  - id: 2_3
+    source: 2
+    detectPoints:
+      - CLIENT
+    target: 3
+  - id: 1_2
+    source: 1
+    detectPoints:
+      - SERVER
+    target: 2
diff --git a/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.traces.yml b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.traces.yml
new file mode 100644
index 0000000..2052aad
--- /dev/null
+++ b/test/e2e/e2e-single-service/src/test/resources/expected-data/org.apache.skywalking.e2e.SampleVerificationITCase.traces.yml
@@ -0,0 +1,31 @@
+# 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.
+
+# 1 health-check by docker-maven-plugin
+# 1 drop table if exists, because we have `ddl-auto: create-drop`
+# 1 drop sequence
+# 1 create sequence
+# 1 create table statement
+
+traces:
+  - key: not null
+    endpointNames:
+      - /e2e/users
+    duration: ge 0
+    start: gt 0
+    isError: false
+    traceIds:
+      - not null
diff --git a/test/e2e/pom.xml b/test/e2e/pom.xml
new file mode 100644
index 0000000..f914a73
--- /dev/null
+++ b/test/e2e/pom.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<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.skywalking</groupId>
+    <artifactId>apache-skywalking-e2e</artifactId>
+    <version>1.0.0</version>
+
+    <name>SkyWalking End to End Tests</name>
+
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>e2e-base</module>
+        <module>e2e-single-service</module>
+    </modules>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+        <spring.boot.version>2.1.5.RELEASE</spring.boot.version>
+        <faster.jackson.version>2.9.7</faster.jackson.version>
+        <junit.version>4.12</junit.version>
+        <jackson.version>2.9.7</jackson.version>
+        <guava.version>28.0-jre</guava.version>
+        <snake.version>1.23</snake.version>
+        <gson.version>2.8.5</gson.version>
+        <h2.version>1.4.199</h2.version>
+
+        <!-- build.id is an available environment variable in Jenkins to
+             distinguish the different build jobs, once Jenkins job is aborted,
+             we will use this build.id to stop all containers that are started
+             during this specific build, see Jenkins-E2E (post stage) for detail
+         -->
+        <build.id>local</build.id>
+
+        <e2e.container.version>1.0.0</e2e.container.version>
+
+        <maven-failsafe-plugin.version>2.22.0</maven-failsafe-plugin.version>
+        <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
+        <docker-maven-plugin.version>0.30.0</docker-maven-plugin.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>${gson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${guava.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-failsafe-plugin</artifactId>
+                    <version>${maven-failsafe-plugin.version}</version>
+                    <executions>
+                        <execution>
+                            <goals>
+                                <goal>verify</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
+                    <groupId>io.fabric8</groupId>
+                    <artifactId>docker-maven-plugin</artifactId>
+                    <version>${docker-maven-plugin.version}</version>
+                    <configuration>
+                        <sourceMode>all</sourceMode>
+                        <showLogs>true</showLogs>
+                        <logDate>default</logDate>
+                        <imagePullPolicy>IfNotPresent</imagePullPolicy>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <id>start</id>
+                            <phase>pre-integration-test</phase>
+                            <goals>
+                                <goal>start</goal>
+                            </goals>
+                        </execution>
+                        <execution>
+                            <id>stop</id>
+                            <phase>post-integration-test</phase>
+                            <goals>
+                                <goal>stop</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>${maven-compiler-plugin.version}</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>${maven-failsafe-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>