This is an automated email from the ASF dual-hosted git repository.
hefengen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new e5bde9c79b [type:test] Add e2e web socket sync (#5264)
e5bde9c79b is described below
commit e5bde9c79b901f922c2fa71e9f616f75ee249765
Author: wenlongbrother <[email protected]>
AuthorDate: Thu Nov 2 13:22:13 2023 +0800
[type:test] Add e2e web socket sync (#5264)
* add websocket client dependency
* update websocket e2e test
* add k8s script
* update sh file
* add cm.yaml
* add e2e to yaml
* remove unused file and update configmap
* update e2e-websocket-sync.sh
* Change the code to comply with the specification
* Change the code to comply with the specification
* Change the code to comply with the specification
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* update code
* add test code to test
* remove unused code
* add test code to test
* add test code to test
* add test code to test
* add test code to test
* update code
* update code
* update code
* update code
* update code
* add healthcheck
* reset
* reset config
* reset config
* reset config
* reset config
* reset config
* reset config
* reset config
* reset config
* update port from 9095 to 31095
---------
Co-authored-by: ”qingge“ <“[email protected]”>
Co-authored-by: xiaoyu <[email protected]>
Co-authored-by: moremind <[email protected]>
---
.github/workflows/e2e-k8s.yml | 2 +
shenyu-e2e/pom.xml | 7 ++
shenyu-e2e/shenyu-e2e-case/pom.xml | 1 +
.../k8s/script/e2e-websocket-sync.sh | 78 ++++++++++++++++++
.../k8s/script/healthcheck.sh | 39 +++++++++
.../k8s/shenyu-examples-websocket.yml | 72 ++++++++++++++++
.../{ => shenyu-e2e-case-websocket}/pom.xml | 33 +++-----
.../e2e/testcase/websocket/DataSynTest.java | 59 +++++++++++++
.../testcase/websocket/WebSocketPluginCases.java | 57 +++++++++++++
.../testcase/websocket/WebSocketPluginTest.java | 83 +++++++++++++++++++
.../shenyu/e2e/client/gateway/GatewayClient.java | 57 ++++++++++++-
.../engine/scenario/function/WebSocketChecker.java | 52 ++++++++++++
.../scenario/function/WebSocketCheckers.java | 96 ++++++++++++++++++++++
.../WebSocketVerifier.java} | 31 ++++---
.../engine/scenario/specification/CaseSpec.java | 6 ++
.../specification/ScenarioSpecLogProxy.java | 9 +-
.../scenario/specification/ShenYuCaseSpec.java | 51 +++++++++++-
17 files changed, 690 insertions(+), 43 deletions(-)
diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml
index 6072527b0a..f0fa44b761 100644
--- a/.github/workflows/e2e-k8s.yml
+++ b/.github/workflows/e2e-k8s.yml
@@ -209,6 +209,8 @@ jobs:
script: e2e-sofa-sync
- case: shenyu-e2e-case-grpc
script: e2e-grpc-sync
+ - case: shenyu-e2e-case-websocket
+ script: e2e-websocket-sync
steps:
- uses: actions/checkout@v2
with:
diff --git a/shenyu-e2e/pom.xml b/shenyu-e2e/pom.xml
index 2e38489c72..7373080925 100644
--- a/shenyu-e2e/pom.xml
+++ b/shenyu-e2e/pom.xml
@@ -57,6 +57,7 @@
<maven-checkstyle-plugin.version>3.1.0</maven-checkstyle-plugin.version>
<guava.version>31.1-jre</guava.version>
<commons-collection.verion>4.4</commons-collection.verion>
+ <websocket.version>1.5.1</websocket.version>
</properties>
<modules>
@@ -67,6 +68,12 @@
</modules>
<dependencies>
+ <dependency>
+ <groupId>org.java-websocket</groupId>
+ <artifactId>Java-WebSocket</artifactId>
+ <version>${websocket.version}</version>
+ </dependency>
+
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
diff --git a/shenyu-e2e/shenyu-e2e-case/pom.xml
b/shenyu-e2e/shenyu-e2e-case/pom.xml
index a3e3e11a52..e72a646df7 100644
--- a/shenyu-e2e/shenyu-e2e-case/pom.xml
+++ b/shenyu-e2e/shenyu-e2e-case/pom.xml
@@ -37,6 +37,7 @@
<module>shenyu-e2e-case-motan</module>
<module>shenyu-e2e-case-grpc</module>
<module>shenyu-e2e-case-brpc</module>
+ <module>shenyu-e2e-case-websocket</module>
</modules>
<dependencies>
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/e2e-websocket-sync.sh
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/e2e-websocket-sync.sh
new file mode 100644
index 0000000000..5d96dd4883
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/e2e-websocket-sync.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+#
+# 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.
+#
+
+docker save shenyu-example-spring-native-websocket:latest | sudo k3s ctr
images import -
+
+# init kubernetes for mysql
+SHENYU_TESTCASE_DIR=$(dirname "$(dirname "$(dirname "$(dirname "$0")")")")
+bash "${SHENYU_TESTCASE_DIR}"/k8s/script/storage/storage_init_mysql.sh
+
+# init register center
+CUR_PATH=$(readlink -f "$(dirname "$0")")
+PRGDIR=$(dirname "$CUR_PATH")
+echo "$PRGDIR"
+kubectl apply -f "${SHENYU_TESTCASE_DIR}"/k8s/sync/shenyu-cm.yml
+
+# init shenyu sync
+SYNC_ARRAY=("websocket" "http" "zookeeper" "etcd")
+#SYNC_ARRAY=("websocket" "nacos")
+MIDDLEWARE_SYNC_ARRAY=("zookeeper" "etcd" "nacos")
+for sync in ${SYNC_ARRAY[@]}; do
+ echo -e "------------------\n"
+ kubectl apply -f "$SHENYU_TESTCASE_DIR"/k8s/shenyu-mysql.yml
+ sleep 30s
+ echo "[Start ${sync} synchronous] create shenyu-admin-${sync}.yml
shenyu-bootstrap-${sync}.yml shenyu-examples-websocket.yml"
+ # shellcheck disable=SC2199
+ # shellcheck disable=SC2076
+ if [[ "${MIDDLEWARE_SYNC_ARRAY[@]}" =~ "${sync}" ]]; then
+ kubectl apply -f "${SHENYU_TESTCASE_DIR}"/k8s/shenyu-"${sync}".yml
+ sleep 10s
+ fi
+ kubectl apply -f "${SHENYU_TESTCASE_DIR}"/k8s/sync/shenyu-admin-"${sync}".yml
+ sh "${CUR_PATH}"/healthcheck.sh http://localhost:31095/actuator/health
+ kubectl apply -f
"${SHENYU_TESTCASE_DIR}"/k8s/sync/shenyu-bootstrap-"${sync}".yml
+ sh "${CUR_PATH}"/healthcheck.sh http://localhost:31195/actuator/health
+ kubectl apply -f "${PRGDIR}"/shenyu-examples-websocket.yml
+ sh "${CUR_PATH}"/healthcheck.sh http://localhost:31191/actuator/health
+ sleep 10s
+ kubectl get pod -o wide
+
+ ## run e2e-test
+ ./mvnw -B -f ./shenyu-e2e/pom.xml -pl
shenyu-e2e-case/shenyu-e2e-case-websocket -am test
+ # shellcheck disable=SC2181
+ if (($?)); then
+ echo "${sync}-sync-e2e-test failed"
+ echo "shenyu-admin log:"
+ echo "------------------"
+ kubectl logs "$(kubectl get pod -o wide | grep shenyu-admin | awk '{print
$1}')"
+ echo "shenyu-bootstrap log:"
+ echo "------------------"
+ kubectl logs "$(kubectl get pod -o wide | grep shenyu-bootstrap | awk
'{print $1}')"
+ exit 1
+ fi
+ kubectl delete -f "${SHENYU_TESTCASE_DIR}"/k8s/shenyu-mysql.yml
+ kubectl delete -f
"${SHENYU_TESTCASE_DIR}"/k8s/sync/shenyu-admin-"${sync}".yml
+ kubectl delete -f
"${SHENYU_TESTCASE_DIR}"/k8s/sync/shenyu-bootstrap-"${sync}".yml
+ kubectl delete -f "${PRGDIR}"/shenyu-examples-websocket.yml
+ # shellcheck disable=SC2199
+ # shellcheck disable=SC2076
+ if [[ "${MIDDLEWARE_SYNC_ARRAY[@]}" =~ "${sync}" ]]; then
+ kubectl delete -f "${SHENYU_TESTCASE_DIR}"/k8s/shenyu-"${sync}".yml
+ fi
+ echo "[Remove ${sync} synchronous] delete shenyu-admin-${sync}.yml
shenyu-bootstrap-${sync}.yml shenyu-examples-websocket.yml"
+done
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/healthcheck.sh
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/healthcheck.sh
new file mode 100644
index 0000000000..1159a0f417
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/script/healthcheck.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# 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.
+#
+
+for loop in $(seq 1 30); do
+ status=$(curl -s -o /dev/null -w %{http_code} -X GET "${1}" -H "accept: */*")
+ echo -e "${loop} curl ${1} response $status"
+ if [ "$status" -eq 200 ]; then
+ break
+ fi
+ sleep 2s
+done
+
+status=$(curl -s -o /dev/null -w "%{http_code}" -X GET "${1}" -H "accept: */*")
+
+if [ "$status" -eq 200 ]; then
+ echo -e "\n-------------------"
+ echo -e "Success to ${1} send request: $status"
+ echo -e "\n-------------------"
+ exit 0
+fi
+echo -e "\n-------------------"
+echo -e "Failed to send request from shenyu-admin : $status"
+echo -e "\n-------------------"
+exit 1
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/shenyu-examples-websocket.yml
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/shenyu-examples-websocket.yml
new file mode 100644
index 0000000000..449e6ad0ed
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/k8s/shenyu-examples-websocket.yml
@@ -0,0 +1,72 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: shenyu-examples-websocket
+ labels:
+ app: shenyu-examples-websocket
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: shenyu-examples-websocket
+ strategy: {}
+ template:
+ metadata:
+ labels:
+ app: shenyu-examples-websocket
+ spec:
+ containers:
+ - image: shenyu-example-spring-native-websocket:latest
+ name: shenyu-examples-websocket
+ livenessProbe:
+ exec:
+ command:
+ - wget -q -O - http://localhost:8868/actuator/health | grep UP
|| exit 1
+ initialDelaySeconds: 30
+ failureThreshold: 3
+ timeoutSeconds: 2
+ env:
+ - name: shenyu.register.serverLists
+ value: http://shenyu-admin:9095
+ - name: websocket.registry.address
+ value: zookeeper://shenyu-zookeeper:2181
+ ports:
+ - containerPort: 8868
+ imagePullPolicy: IfNotPresent
+ restartPolicy: Always
+status: {}
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: shenyu-examples-websocket
+ labels:
+ app: shenyu-examples-websocket
+spec:
+ selector:
+ app: shenyu-examples-websocket
+ type: NodePort
+ ports:
+ - name: "8868"
+ port: 8868
+ targetPort: 8868
+ nodePort: 31191
+status:
+ loadBalancer: {}
diff --git a/shenyu-e2e/shenyu-e2e-case/pom.xml
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/pom.xml
similarity index 54%
copy from shenyu-e2e/shenyu-e2e-case/pom.xml
copy to shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/pom.xml
index a3e3e11a52..024fd51c23 100644
--- a/shenyu-e2e/shenyu-e2e-case/pom.xml
+++ b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/pom.xml
@@ -15,36 +15,29 @@
~ 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="https://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
+<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>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-e2e</artifactId>
+ <artifactId>shenyu-e2e-case</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
- <artifactId>shenyu-e2e-case</artifactId>
+ <modelVersion>4.0.0</modelVersion>
- <packaging>pom</packaging>
+ <artifactId>shenyu-e2e-case-websocket</artifactId>
- <modules>
- <module>shenyu-e2e-case-http</module>
- <module>shenyu-e2e-case-spring-cloud</module>
- <module>shenyu-e2e-case-alibaba-dubbo</module>
- <module>shenyu-e2e-case-apache-dubbo</module>
- <module>shenyu-e2e-case-sofa</module>
- <module>shenyu-e2e-case-motan</module>
- <module>shenyu-e2e-case-grpc</module>
- <module>shenyu-e2e-case-brpc</module>
- </modules>
+ <properties>
+ <websocket.version>1.5.1</websocket.version>
+ </properties>
<dependencies>
<dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-e2e-engine</artifactId>
- <version>${project.version}</version>
+ <groupId>org.java-websocket</groupId>
+ <artifactId>Java-WebSocket</artifactId>
+ <version>1.5.3</version>
</dependency>
-
</dependencies>
+
</project>
\ No newline at end of file
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/DataSynTest.java
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/DataSynTest.java
new file mode 100644
index 0000000000..8f04ce167e
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/DataSynTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.shenyue.e2e.testcase.websocket;
+
+import org.apache.shenyu.e2e.client.WaitDataSync;
+import org.apache.shenyu.e2e.client.admin.AdminClient;
+import org.apache.shenyu.e2e.client.gateway.GatewayClient;
+import org.apache.shenyu.e2e.engine.annotation.ShenYuTest;
+import org.apache.shenyu.e2e.enums.ServiceTypeEnum;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Testing the correctness of etcd data synchronization method.
+ */
+@ShenYuTest(environments = {
+ @ShenYuTest.Environment(
+ serviceName = "shenyu-e2e-admin",
+ service = @ShenYuTest.ServiceConfigure(moduleName =
"shenyu-e2e",
+ baseUrl = "http://localhost:31095",
+ type = ServiceTypeEnum.SHENYU_ADMIN,
+ parameters = {
+ @ShenYuTest.Parameter(key = "username", value
= "admin"),
+ @ShenYuTest.Parameter(key = "password", value
= "123456")
+ }
+ )
+ ),
+ @ShenYuTest.Environment(
+ serviceName = "shenyu-e2e-gateway",
+ service = @ShenYuTest.ServiceConfigure(moduleName =
"shenyu-e2e",
+ baseUrl = "http://localhost:31195",
+ type = ServiceTypeEnum.SHENYU_GATEWAY
+ )
+ )
+})
+public class DataSynTest {
+
+ @Test
+ void testDataSyn(final AdminClient adminClient, final GatewayClient
gatewayClient) throws Exception {
+ adminClient.login();
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllSelectors,
gatewayClient::getSelectorCache, adminClient);
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllMetaData,
gatewayClient::getMetaDataCache, adminClient);
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllRules,
gatewayClient::getRuleCache, adminClient);
+ }
+}
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginCases.java
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginCases.java
new file mode 100644
index 0000000000..153a068e64
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginCases.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.shenyue.e2e.testcase.websocket;
+
+import com.google.common.collect.Lists;
+import org.apache.shenyu.e2e.engine.scenario.ShenYuScenarioProvider;
+import org.apache.shenyu.e2e.engine.scenario.specification.ScenarioSpec;
+import
org.apache.shenyu.e2e.engine.scenario.specification.ShenYuBeforeEachSpec;
+import org.apache.shenyu.e2e.engine.scenario.specification.ShenYuCaseSpec;
+import org.apache.shenyu.e2e.engine.scenario.specification.ShenYuScenarioSpec;
+
+import java.util.List;
+
+import static
org.apache.shenyu.e2e.engine.scenario.function.WebSocketCheckers.exists;
+
+public class WebSocketPluginCases implements ShenYuScenarioProvider {
+
+ @Override
+ public List<ScenarioSpec> get() {
+ return Lists.newArrayList(
+ testWebSocket()
+ );
+ }
+
+ /**
+ * test websocket.
+ *
+ * @return ShenYuScenarioSpec
+ */
+ public ShenYuScenarioSpec testWebSocket() {
+ return ShenYuScenarioSpec.builder()
+ .name("single-websocket test]")
+ .beforeEachSpec(ShenYuBeforeEachSpec.builder()
+ .checker(exists("/ws-native/myWebSocket?token=Jack",
"Hello ShenYu", "apache shenyu server send to Jack message : -> Hello ShenYu"))
+ .build())
+ .caseSpec(ShenYuCaseSpec.builder()
+ .addExists("/ws-native/myWebSocket?token=Mask", "Hello
ShenYu", "apache shenyu server send to Mask message : -> Hello ShenYu")
+ .addNotExists("/ws-annotation/myWs", "Hello ShenYu")
+ .build())
+ .build();
+ }
+}
diff --git
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginTest.java
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginTest.java
new file mode 100644
index 0000000000..1b2cdc2bc8
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-websocket/src/test/java/org/apache/shenyue/e2e/testcase/websocket/WebSocketPluginTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.shenyue.e2e.testcase.websocket;
+
+import org.apache.shenyu.e2e.client.WaitDataSync;
+import org.apache.shenyu.e2e.client.admin.AdminClient;
+import org.apache.shenyu.e2e.client.gateway.GatewayClient;
+import org.apache.shenyu.e2e.engine.annotation.ShenYuScenario;
+import org.apache.shenyu.e2e.engine.annotation.ShenYuTest;
+import org.apache.shenyu.e2e.engine.scenario.specification.CaseSpec;
+import org.apache.shenyu.e2e.enums.ServiceTypeEnum;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+@ShenYuTest(environments = {
+ @ShenYuTest.Environment(
+ serviceName = "shenyu-e2e-admin",
+ service = @ShenYuTest.ServiceConfigure(moduleName =
"shenyu-e2e",
+ baseUrl = "http://localhost:31095",
+ type = ServiceTypeEnum.SHENYU_ADMIN,
+ parameters = {
+ @ShenYuTest.Parameter(key = "username", value
= "admin"),
+ @ShenYuTest.Parameter(key = "password", value
= "123456")
+ }
+ )
+ ),
+ @ShenYuTest.Environment(
+ serviceName = "shenyu-e2e-gateway",
+ service = @ShenYuTest.ServiceConfigure(moduleName =
"shenyu-e2e",
+ baseUrl = "http://localhost:31195",
+ type = ServiceTypeEnum.SHENYU_GATEWAY
+ )
+ )
+})
+/**
+ * Testing websocket plugin.
+ */
+public class WebSocketPluginTest {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(WebSocketPluginTest.class);
+
+ @BeforeAll
+ static void setup(final AdminClient adminClient, final GatewayClient
gatewayClient) throws Exception {
+ adminClient.login();
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllRules,
gatewayClient::getRuleCache, adminClient);
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllSelectors,
gatewayClient::getSelectorCache, adminClient);
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllMetaData,
gatewayClient::getMetaDataCache, adminClient);
+
WaitDataSync.waitAdmin2GatewayDataSyncEquals(adminClient::listAllRules,
gatewayClient::getRuleCache, adminClient);
+ LOG.info("start websocket plugin");
+ MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+ formData.add("id", "26");
+ formData.add("name", "websocket");
+ formData.add("enabled", "true");
+ formData.add("role", "Proxy");
+ formData.add("sort", "200");
+ adminClient.changePluginStatus("1", formData);
+ WaitDataSync.waitGatewayPluginUse(gatewayClient,
"org.apache.shenyu.plugin.websocket.WebSocketPlugin");
+
+ }
+
+ @ShenYuScenario(provider = WebSocketPluginCases.class)
+ void testWebSocket(final GatewayClient gateway, final CaseSpec spec) {
+ spec.getWebSocketVerifiers().forEach(webSocketVerifier ->
webSocketVerifier.verify(gateway.getWebSocketClientSupplier().get(), gateway));
+ }
+}
diff --git
a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/gateway/GatewayClient.java
b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/gateway/GatewayClient.java
index 8966e3b05c..9acdc54d10 100644
---
a/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/gateway/GatewayClient.java
+++
b/shenyu-e2e/shenyu-e2e-client/src/main/java/org/apache/shenyu/e2e/client/gateway/GatewayClient.java
@@ -27,6 +27,8 @@ import org.apache.shenyu.e2e.common.RequestLogConsumer;
import org.apache.shenyu.e2e.model.data.MetaData;
import org.apache.shenyu.e2e.model.data.RuleCacheData;
import org.apache.shenyu.e2e.model.data.SelectorCacheData;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
@@ -34,10 +36,14 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import static io.restassured.RestAssured.given;
@@ -53,7 +59,9 @@ public class GatewayClient extends BaseClient {
private static final RestTemplate TEMPLATE = new RestTemplate();
private static final ObjectMapper MAPPER = new ObjectMapper();
-
+
+ private static final ArrayBlockingQueue<String> BLOCKING_QUEUE = new
ArrayBlockingQueue<>(1);
+
private final String scenarioId;
private final String baseUrl;
@@ -104,6 +112,41 @@ public class GatewayClient extends BaseClient {
})
.when();
}
+
+ /**
+ * get websocket client.
+ * @return Supplier
+ */
+ public Supplier<WebSocketClient> getWebSocketClientSupplier() {
+ return () -> {
+ try {
+ return new WebSocketClient(new
URI(getBaseUrl().replaceAll("http", "ws"))) {
+ @Override
+ public void onOpen(final ServerHandshake handshakeData) {
+ log.info("Open websocket connection successfully");
+ }
+
+ @Override
+ public void onMessage(final String message) {
+ BLOCKING_QUEUE.add(message);
+ log.info("Receive Message: " + message);
+ }
+
+ @Override
+ public void onClose(final int code, final String reason,
final boolean remote) {
+ }
+
+ @Override
+ public void onError(final Exception ex) {
+ log.error(ex.getMessage());
+ }
+ };
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Invalid WebSocket URI", e);
+ }
+ };
+
+ }
/**
* get meta data cache.
@@ -172,4 +215,16 @@ public class GatewayClient extends BaseClient {
List body = response.getBody();
return (Map<String, Integer>) body.get(0);
}
+
+ /**
+ * get the return message from the server.
+ * @return String String
+ */
+ public String getWebSocketMessage() {
+ try {
+ return BLOCKING_QUEUE.poll(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketChecker.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketChecker.java
new file mode 100644
index 0000000000..fe759567c3
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketChecker.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.shenyu.e2e.engine.scenario.function;
+
+import org.apache.shenyu.e2e.client.gateway.GatewayClient;
+import org.java_websocket.client.WebSocketClient;
+import org.slf4j.MDC;
+
+import java.util.function.Supplier;
+
+/**
+ * WebSocket Checker interface.
+ */
+@FunctionalInterface
+public interface WebSocketChecker extends Checker, WebSocketVerifier {
+
+ /**
+ * check websocket client.
+ * @param client client
+ */
+ default void check(GatewayClient client) {
+ check(client.getWebSocketClientSupplier(), client);
+ }
+
+ /**
+ * check request specification.
+ * @param supplier supplier
+ * @param client client
+ */
+ default void check(Supplier<WebSocketClient> supplier, GatewayClient
client) {
+ try {
+ verify(supplier.get(), client);
+ } catch (AssertionError e) {
+ throw new AssertionError("failed to request " +
MDC.get("endpoint"), e);
+ }
+ }
+}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketCheckers.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketCheckers.java
new file mode 100644
index 0000000000..10fe560a2a
--- /dev/null
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketCheckers.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.shenyu.e2e.engine.scenario.function;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.exceptions.WebsocketNotConnectedException;
+import org.junit.jupiter.api.Assertions;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Optional;
+
+/**
+ * Check if the endpoint exists.
+ */
+public class WebSocketCheckers {
+
+ /**
+ * exist endpoint.
+ * @param endpoint endpoint
+ * @param sendMessage sendMessage
+ * @param receiveMessage receiveMessage
+ * @return WebSocketChecker
+ */
+ public static WebSocketChecker exists(final String endpoint, final String
sendMessage, final String receiveMessage) {
+ return (websocketClient, gatewayClient) -> {
+ try {
+ updateWebSocketClientURI(websocketClient, endpoint);
+
+ websocketClient.connectBlocking();
+ websocketClient.send(sendMessage);
+ Optional.of(websocketClient)
+ .filter(WebSocketClient::isOpen)
+ .ifPresent(client ->
Assertions.assertEquals(receiveMessage, gatewayClient.getWebSocketMessage()));
+ } catch (AssertionError | InterruptedException | RuntimeException
error) {
+ Assertions.fail("websocket endpoint '" +
websocketClient.getURI() + "' not exists", error);
+ } catch (NoSuchFieldException | IllegalAccessException |
URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+
+ /**
+ * not exist endpoint.
+ * @param endpoint endpoint
+ * @param message message
+ * @return WebSocketChecker
+ */
+ public static WebSocketChecker notExists(final String endpoint, final
String message) {
+ return (websocketClient, gatewayClient) -> {
+ try {
+ updateWebSocketClientURI(websocketClient, endpoint);
+
+ websocketClient.connectBlocking();
+ Optional.of(websocketClient)
+ .filter(WebSocketClient::isOpen)
+ .ifPresent(client -> Assertions.fail("websocket
endpoint '" + endpoint + "' exists"));
+ } catch (AssertionError | InterruptedException |
WebsocketNotConnectedException error) {
+ Assertions.fail("websocket endpoint '" + endpoint + "' not
exists", error);
+ } catch (URISyntaxException | NoSuchFieldException |
IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+
+
+ /**
+ * update websocket client uri.
+ * @param client client
+ * @param endpoint endpoint
+ */
+ private static void updateWebSocketClientURI(final WebSocketClient client,
final String endpoint)
+ throws NoSuchFieldException, IllegalAccessException,
URISyntaxException {
+ Field uriField = WebSocketClient.class.getDeclaredField("uri");
+ uriField.setAccessible(true);
+ URI originalUri = client.getURI();
+ URI updatedUri = new URI(originalUri + endpoint);
+ uriField.set(client, updatedUri);
+ }
+}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketVerifier.java
similarity index 62%
copy from
shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
copy to
shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketVerifier.java
index cb30193e5d..4464ac4ec3 100644
---
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/function/WebSocketVerifier.java
@@ -15,26 +15,23 @@
* limitations under the License.
*/
-package org.apache.shenyu.e2e.engine.scenario.specification;
+package org.apache.shenyu.e2e.engine.scenario.function;
-import org.apache.shenyu.e2e.engine.annotation.ShenYuScenarioParameter;
-import org.apache.shenyu.e2e.engine.scenario.function.Verifier;
+import org.apache.shenyu.e2e.client.gateway.GatewayClient;
+import org.java_websocket.client.WebSocketClient;
-import java.util.List;
+/**
+ * WebSocket Verifier interface.
+ */
+public interface WebSocketVerifier {
+
+ WebSocketVerifier DEFAULT = (webSocketClient, gatewayClient) -> {
+ };
-@ShenYuScenarioParameter
-public interface CaseSpec {
-
- /**
- * get case spec name.
- * @return String
- */
- String getName();
-
/**
- * get case spec verifiers.
- * @return List
+ * Verify WebSocketClient.
+ * @param client WebSocketClient
+ * @param gatewayClient GatewayClient
*/
- List<Verifier> getVerifiers();
-
+ void verify(WebSocketClient client, GatewayClient gatewayClient);
}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
index cb30193e5d..8e1314d52e 100644
---
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/CaseSpec.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.e2e.engine.scenario.specification;
import org.apache.shenyu.e2e.engine.annotation.ShenYuScenarioParameter;
import org.apache.shenyu.e2e.engine.scenario.function.Verifier;
+import org.apache.shenyu.e2e.engine.scenario.function.WebSocketVerifier;
import java.util.List;
@@ -37,4 +38,9 @@ public interface CaseSpec {
*/
List<Verifier> getVerifiers();
+ /**
+ * get case spec websocket verifiers.
+ * @return List
+ */
+ List<WebSocketVerifier> getWebSocketVerifiers();
}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ScenarioSpecLogProxy.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ScenarioSpecLogProxy.java
index 0bbc75721d..472d5926a9 100644
---
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ScenarioSpecLogProxy.java
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ScenarioSpecLogProxy.java
@@ -17,11 +17,12 @@
package org.apache.shenyu.e2e.engine.scenario.specification;
-import org.apache.shenyu.e2e.model.ResourcesData;
import org.apache.shenyu.e2e.engine.scenario.function.Checker;
import org.apache.shenyu.e2e.engine.scenario.function.Deleter;
import org.apache.shenyu.e2e.engine.scenario.function.Verifier;
import org.apache.shenyu.e2e.engine.scenario.function.Waiting;
+import org.apache.shenyu.e2e.engine.scenario.function.WebSocketVerifier;
+import org.apache.shenyu.e2e.model.ResourcesData;
import org.slf4j.MDC;
import java.util.List;
@@ -74,6 +75,12 @@ public class ScenarioSpecLogProxy implements ScenarioSpec {
MDC.put("operate", "verify");
return spec.getVerifiers();
}
+
+ @Override
+ public List<WebSocketVerifier> getWebSocketVerifiers() {
+ MDC.put("operate", "websocketVerify");
+ return spec.getWebSocketVerifiers();
+ }
};
}
diff --git
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ShenYuCaseSpec.java
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ShenYuCaseSpec.java
index 89d138183d..09acc05c51 100644
---
a/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ShenYuCaseSpec.java
+++
b/shenyu-e2e/shenyu-e2e-engine/src/main/java/org/apache/shenyu/e2e/engine/scenario/specification/ShenYuCaseSpec.java
@@ -21,6 +21,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import io.restassured.http.Method;
import org.apache.shenyu.e2e.engine.scenario.function.Verifier;
+import org.apache.shenyu.e2e.engine.scenario.function.WebSocketCheckers;
+import org.apache.shenyu.e2e.engine.scenario.function.WebSocketVerifier;
import org.hamcrest.Matcher;
import java.util.List;
@@ -38,9 +40,12 @@ public class ShenYuCaseSpec implements CaseSpec {
private final List<Verifier> verifiers;
- public ShenYuCaseSpec(final String name, final List<Verifier> verifiers) {
+ private final List<WebSocketVerifier> webSocketVerifiers;
+
+ public ShenYuCaseSpec(final String name, final List<Verifier> verifiers,
final List<WebSocketVerifier> webSocketVerifiers) {
this.name = name;
this.verifiers = verifiers;
+ this.webSocketVerifiers = webSocketVerifiers;
}
/**
@@ -62,6 +67,11 @@ public class ShenYuCaseSpec implements CaseSpec {
public List<Verifier> getVerifiers() {
return verifiers;
}
+
+ @Override
+ public List<WebSocketVerifier> getWebSocketVerifiers() {
+ return webSocketVerifiers;
+ }
/**
* builder.
@@ -86,6 +96,8 @@ public class ShenYuCaseSpec implements CaseSpec {
private final Builder<Verifier> builder = ImmutableList.builder();
+ private final Builder<WebSocketVerifier> webSocketBuilder =
ImmutableList.builder();
+
public ShenYuTestCaseSpecBuilder() {
}
@@ -112,6 +124,16 @@ public class ShenYuCaseSpec implements CaseSpec {
builder.add(verifier);
return this;
}
+
+ /**
+ * websocket builder add verifier.
+ * @param webSocketVerifier webSocketVerifier
+ * @return ShenYuTestCaseSpecBuilder
+ */
+ public ShenYuTestCaseSpecBuilder add(final WebSocketVerifier
webSocketVerifier) {
+ webSocketBuilder.add(webSocketVerifier);
+ return this;
+ }
/**
* add verifier case spec.
@@ -133,7 +155,7 @@ public class ShenYuCaseSpec implements CaseSpec {
* @return ShenYuTestCaseSpecBuilder
*/
public ShenYuTestCaseSpecBuilder addVerifier(final Method method,
final String endpoint, final Matcher<?> matcher, final Matcher<?>... matchers) {
- return add(supplier -> supplier.when().request(method,
endpoint).then().assertThat().body(matcher, matchers));
+ return add((Verifier) supplier -> supplier.when().request(method,
endpoint).then().assertThat().body(matcher, matchers));
}
/**
@@ -155,6 +177,17 @@ public class ShenYuCaseSpec implements CaseSpec {
return add(exists(method, endpoint));
}
+ /**
+ * add exist method endpoint case spec.
+ * @param endpoint endpoint
+ * @param sendMessage sendMessage
+ * @param receiveMessage receiveMessage
+ * @return ShenYuTestCaseSpecBuilder
+ */
+ public ShenYuTestCaseSpecBuilder addExists(final String endpoint,
final String sendMessage, final String receiveMessage) {
+ return add(WebSocketCheckers.exists(endpoint, sendMessage,
receiveMessage));
+ }
+
/**
* add exist method endpoint case spec.
@@ -186,13 +219,23 @@ public class ShenYuCaseSpec implements CaseSpec {
public ShenYuTestCaseSpecBuilder addNotExists(final Method method,
final String endpoint) {
return add(notExists(method, endpoint));
}
-
+
+ /**
+ * add not exists case spec.
+ * @param endpoint endpoint
+ * @param message message
+ * @return ShenYuTestCaseSpecBuilder
+ */
+ public ShenYuTestCaseSpecBuilder addNotExists(final String endpoint,
final String message) {
+ return add(WebSocketCheckers.notExists(endpoint, message));
+ }
+
/**
* build.
* @return ShenYuCaseSpec
*/
public ShenYuCaseSpec build() {
- return new ShenYuCaseSpec(name, builder.build());
+ return new ShenYuCaseSpec(name, builder.build(),
webSocketBuilder.build());
}
}
}