You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by li...@apache.org on 2024/02/27 14:24:53 UTC

(shenyu) branch master updated: [ISSUE #5427] add AbstractShenyuWasmPluginTest (#5450)

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

likeguo 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 5d9fb45005 [ISSUE #5427] add AbstractShenyuWasmPluginTest (#5450)
5d9fb45005 is described below

commit 5d9fb450050e3e54bf6e5a758abb0dc2ce67fb1d
Author: loongs-zhang <zh...@apache.org>
AuthorDate: Tue Feb 27 22:24:47 2024 +0800

    [ISSUE #5427] add AbstractShenyuWasmPluginTest (#5450)
    
    * [ISSUE #5427] add AbstractShenyuWasmPluginTest
    
    * fix checkstyle
    
    * fix checkstyle
    
    * fix checkstyle
    
    ---------
    
    Co-authored-by: xiaoyu <xi...@apache.org>
---
 .../plugin/wasm/base/AbstractShenyuWasmPlugin.java |  10 +
 .../wasm/base/AbstractShenyuWasmPluginTest.java    | 347 +++++++++++++++++++++
 ...tShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm | Bin 0 -> 1679614 bytes
 .../src/test/rust-shenyu-wasm-plugin/Cargo.toml    |  26 ++
 .../src/test/rust-shenyu-wasm-plugin/README.md     |  19 ++
 .../src/test/rust-shenyu-wasm-plugin/src/lib.rs    |  38 +++
 6 files changed, 440 insertions(+)

diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/main/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPlugin.java b/shenyu-plugin/shenyu-plugin-wasm-base/src/main/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPlugin.java
index ae8198ebb0..1f8ddbabc8 100644
--- a/shenyu-plugin/shenyu-plugin-wasm-base/src/main/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPlugin.java
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/main/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPlugin.java
@@ -36,6 +36,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
+import java.nio.ByteBuffer;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -70,6 +71,15 @@ public abstract class AbstractShenyuWasmPlugin extends AbstractShenyuPlugin {
         return null;
     }
     
+    /**
+     * use this in wasmCallJavaFunc.
+     *
+     * @return the ByteBuffer
+     */
+    public ByteBuffer getBuffer() {
+        return wasmLoader.getBuffer();
+    }
+    
     @Override
     protected Mono<Void> doExecute(final ServerWebExchange exchange,
                                    final ShenyuPluginChain chain,
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPluginTest.java b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPluginTest.java
new file mode 100644
index 0000000000..4adf369869
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/AbstractShenyuWasmPluginTest.java
@@ -0,0 +1,347 @@
+/*
+ * 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.plugin.wasm.base;
+
+import io.github.kawamuray.wasmtime.Func;
+import io.github.kawamuray.wasmtime.Store;
+import io.github.kawamuray.wasmtime.WasmFunctions;
+import io.github.kawamuray.wasmtime.WasmValType;
+import org.apache.shenyu.common.config.ShenyuConfig;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.ConditionData;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.SelectorTypeEnum;
+import org.apache.shenyu.common.enums.TrieCacheTypeEnum;
+import org.apache.shenyu.common.enums.TrieMatchModeEnum;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.base.cache.BaseDataCache;
+import org.apache.shenyu.plugin.base.cache.MatchDataCache;
+import org.apache.shenyu.plugin.base.trie.ShenyuTrie;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * The Abstract shenyu plugin test.
+ */
+public final class AbstractShenyuWasmPluginTest {
+    
+    private RuleData ruleData;
+    
+    private PluginData pluginData;
+    
+    private SelectorData selectorData;
+    
+    private ConditionData conditionData;
+    
+    private ServerWebExchange exchange;
+    
+    private TestShenyuWasmPlugin testShenyuWasmPlugin;
+    
+    private ShenyuPluginChain shenyuPluginChain;
+    
+    @BeforeEach
+    public void setUp() {
+        mockShenyuConfig();
+        this.ruleData = RuleData.builder()
+                .id("1")
+                .pluginName("SHENYU")
+                .selectorId("1")
+                .enabled(true)
+                .loged(true)
+                .matchRestful(false)
+                .sort(1).build();
+        this.conditionData = new ConditionData();
+        this.conditionData.setOperator("match");
+        this.conditionData.setParamName("/");
+        this.conditionData.setParamType("uri");
+        this.conditionData.setParamValue("/http/**");
+        this.shenyuPluginChain = mock(ShenyuPluginChain.class);
+        this.pluginData = PluginData.builder()
+                .name("SHENYU")
+                .enabled(true).build();
+        this.selectorData = SelectorData.builder()
+                .id("1").pluginName("SHENYU")
+                .enabled(true)
+                .matchRestful(false)
+                .type(SelectorTypeEnum.CUSTOM_FLOW.getCode()).build();
+        this.testShenyuWasmPlugin = spy(new TestShenyuWasmPlugin());
+        this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/http/SHENYU/SHENYU")
+                .build());
+        ShenyuContext context = mock(ShenyuContext.class);
+        exchange.getAttributes().put(Constants.CONTEXT, context);
+        clearCache();
+        when(shenyuPluginChain.execute(exchange)).thenReturn(Mono.empty());
+    }
+    
+    /**
+     * The plugin is null test.
+     */
+    @Test
+    public void executePluginIsNullTest() {
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(shenyuPluginChain).execute(exchange);
+    }
+    
+    /**
+     * The selector is null test.
+     */
+    @Test
+    public void executeSelectorIsNullTest() {
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(shenyuPluginChain).execute(exchange);
+    }
+    
+    /**
+     * The selector data is null test.
+     */
+    @Test
+    public void executeSelectorDataIsNullTest() {
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(shenyuPluginChain).execute(exchange);
+    }
+    
+    /**
+     * The rule is null test.
+     */
+    @Test
+    public void executeRuleIsNullTest() {
+        List<ConditionData> conditionDataList = Collections.singletonList(conditionData);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setMatchRestful(false);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(shenyuPluginChain).execute(exchange);
+    }
+    
+    /**
+     * The rule is not null test.
+     */
+    @Test
+    public void executeRuleIsNotNullTest() {
+        List<ConditionData> conditionDataList = Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.ruleData.setMatchRestful(false);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setMatchRestful(false);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(testShenyuWasmPlugin).doExecute(exchange, shenyuPluginChain, selectorData, ruleData);
+    }
+    
+    @Test
+    public void executeSelectorManyMatch() {
+        List<ConditionData> conditionDataList = Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheSelectData(SelectorData.builder()
+                .id("2").pluginName("SHENYU")
+                .enabled(true)
+                .matchMode(0)
+                .logged(true)
+                .sort(2)
+                .conditionList(conditionDataList)
+                .type(SelectorTypeEnum.CUSTOM_FLOW.getCode()).build());
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(testShenyuWasmPlugin).doExecute(exchange, shenyuPluginChain, selectorData, ruleData);
+    }
+    
+    @Test
+    public void executeRuleManyMatch() {
+        List<ConditionData> conditionDataList = Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.ruleData.setMatchRestful(false);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        
+        BaseDataCache.getInstance().cacheRuleData(RuleData.builder()
+                .id("1")
+                .pluginName("SHENYU")
+                .selectorId("1")
+                .enabled(true)
+                .loged(true)
+                .matchMode(0)
+                .matchRestful(false)
+                .conditionDataList(Collections.singletonList(conditionData))
+                .sort(1).build());
+        
+        BaseDataCache.getInstance().cacheRuleData(RuleData.builder()
+                .id("2")
+                .pluginName("SHENYU")
+                .selectorId("1")
+                .enabled(true)
+                .loged(true)
+                .matchMode(0)
+                .matchRestful(false)
+                .conditionDataList(Collections.singletonList(conditionData))
+                .sort(2).build());
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(testShenyuWasmPlugin).doExecute(exchange, shenyuPluginChain, selectorData, ruleData);
+    }
+    
+    /**
+     * The rule is full test.
+     */
+    @Test
+    public void executeRuleFullTest() {
+        List<ConditionData> conditionDataList = Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(1);
+        this.ruleData.setMatchRestful(false);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setMatchRestful(false);
+        this.selectorData.setType(SelectorTypeEnum.FULL_FLOW.getCode());
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        StepVerifier.create(testShenyuWasmPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(testShenyuWasmPlugin).doExecute(exchange, shenyuPluginChain, selectorData, ruleData);
+    }
+    
+    private void mockShenyuConfig() {
+        ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuConfig.class)).thenReturn(new ShenyuConfig());
+        when(context.getBean(TrieCacheTypeEnum.RULE.getTrieType())).thenReturn(new ShenyuTrie(100L, TrieMatchModeEnum.ANT_PATH_MATCH.getMatchMode()));
+        when(context.getBean(TrieCacheTypeEnum.SELECTOR.getTrieType())).thenReturn(new ShenyuTrie(100L, TrieMatchModeEnum.ANT_PATH_MATCH.getMatchMode()));
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+    }
+    
+    @AfterEach
+    public void clear() {
+        MatchDataCache.getInstance().cleanSelectorData();
+        MatchDataCache.getInstance().cleanRuleDataData();
+    }
+    
+    private void clearCache() {
+        BaseDataCache.getInstance().cleanPluginData();
+        BaseDataCache.getInstance().cleanSelectorData();
+        BaseDataCache.getInstance().cleanRuleData();
+    }
+    
+    static class TestShenyuWasmPlugin extends AbstractShenyuWasmPlugin {
+        
+        private static final Map<Long, String> RESULTS = new ConcurrentHashMap<>();
+        
+        @Override
+        protected Map<String, Func> initWasmCallJavaFunc(final Store<Void> store) {
+            Map<String, Func> funcMap = new HashMap<>();
+            funcMap.put("get_args", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32,
+                (argId, addr, len) -> {
+                    String config = "hello from java " + argId;
+                    LOG.info("java side->" + config);
+                    assertEquals("hello from java 0", config);
+                    ByteBuffer buf = super.getBuffer();
+                    for (int i = 0; i < len && i < config.length(); i++) {
+                        buf.put(addr.intValue() + i, (byte) config.charAt(i));
+                    }
+                    return Math.min(config.length(), len);
+                }));
+            funcMap.put("put_result", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32,
+                (argId, addr, len) -> {
+                    ByteBuffer buf = super.getBuffer();
+                    byte[] bytes = new byte[len];
+                    for (int i = 0; i < len; i++) {
+                        bytes[i] = buf.get(addr.intValue() + i);
+                    }
+                    String result = new String(bytes, StandardCharsets.UTF_8);
+                    assertEquals("rust result", result);
+                    RESULTS.put(argId, result);
+                    LOG.info("java side->" + result);
+                    return 0;
+                }));
+            return funcMap;
+        }
+        
+        @Override
+        protected Mono<Void> doExecute(final ServerWebExchange exchange,
+                                       final ShenyuPluginChain chain,
+                                       final SelectorData selector,
+                                       final RuleData rule,
+                                       final Long argumentId) {
+            final String result = RESULTS.get(argumentId);
+            assertEquals("rust result", result);
+            return Mono.empty();
+        }
+        
+        @Override
+        protected Long getArgumentId(final ServerWebExchange exchange,
+                                     final ShenyuPluginChain chain,
+                                     final SelectorData selector,
+                                     final RuleData rule) {
+            return 0L;
+        }
+        
+        @Override
+        public int getOrder() {
+            return 0;
+        }
+        
+        @Override
+        public String named() {
+            return "SHENYU";
+        }
+    }
+}
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm
new file mode 100755
index 0000000000..866f6da67b
Binary files /dev/null and b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm differ
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/Cargo.toml b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/Cargo.toml
new file mode 100644
index 0000000000..054b86990d
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/Cargo.toml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+[package]
+name = "rust-shenyu-wasm-plugin"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/README.md b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/README.md
new file mode 100644
index 0000000000..e4c1abde21
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/README.md
@@ -0,0 +1,19 @@
+# How to build the wasm file
+
+1. install rustup
+
+2. install rust
+
+3. generate the wasm file
+
+```shell
+cd {shenyu}/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin
+cargo build --target wasm32-wasi --release
+```
+
+then you will see the wasm file
+in `{shenyu}/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/target/wasm32-wasi/release/rust_shenyu_wasm_plugin.wasm`
+
+4. rename the wasm file
+
+rename the file to `org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm`
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/src/lib.rs b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/src/lib.rs
new file mode 100644
index 0000000000..b13e8d6bb0
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-shenyu-wasm-plugin/src/lib.rs
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#[link(wasm_import_module = "shenyu")]
+extern "C" {
+    fn get_args(arg_id: i64, addr: i64, len: i32) -> i32;
+
+    fn put_result(arg_id: i64, addr: i64, len: i32) -> i32;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn doExecute(arg_id: i64) {
+    let mut buf = [0u8; 32];
+    let buf_ptr = buf.as_mut_ptr() as i64;
+    eprintln!("rust side-> buffer base address: {}", buf_ptr);
+    // get arg from java
+    let len = get_args(arg_id, buf_ptr, buf.len() as i32);
+    let java_arg = std::str::from_utf8(&buf[..len as usize]).unwrap();
+    eprintln!("rust side-> recv:{}", java_arg);
+    // pass rust result to java
+    let rust_result = "rust result".as_bytes();
+    let result_ptr = rust_result.as_ptr() as i64;
+    _ = put_result(arg_id, result_ptr, rust_result.len() as i32);
+}