This is an automated email from the ASF dual-hosted git repository. jimin pushed a commit to branch 2.x in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push: new ac99e5bebe test: add UT for namingserver module (#7330) ac99e5bebe is described below commit ac99e5bebe0db8f75763c2e2c0c874df3f2dbb0c Author: Soyan <523420...@qq.com> AuthorDate: Thu May 8 14:42:26 2025 +0800 test: add UT for namingserver module (#7330) --- changes/en-us/2.x.md | 3 +- changes/zh-cn/2.x.md | 3 +- .../namingserver/ClusterWatcherManagerTest.java | 199 ++++++++++++ .../seata/namingserver/NamingControllerTest.java | 166 +++++++++- .../seata/namingserver/NamingEntityTest.java | 206 ++++++++++++ .../seata/namingserver/NamingManagerTest.java | 354 +++++++++++++++++++++ 6 files changed, 913 insertions(+), 18 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index bc4c9ced24..85e55f0f94 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -60,7 +60,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#7287](https://github.com/apache/incubator-seata/pull/7287)] Refactored testGetErrorMsgWithValidCodeReturnsExpectedMsg to use parameterized unit testing - [[#7294](https://github.com/apache/incubator-seata/pull/7294)] improved test testGetInsertParamsValue in SqlServerInsertRecognizerTest by splitting and parameterizing - [[#7295](https://github.com/apache/incubator-seata/pull/7295)] updated 3 tests in StringUtilsTest to use parameterized unit testing - +- [[#7205](https://github.com/apache/incubator-seata/issues/7205)] add UT for namingserver module ### refactor: - [[#7315](https://github.com/apache/incubator-seata/pull/7315)] Refactor log testing to use ListAppender for more accurate and efficient log capture @@ -80,5 +80,6 @@ Thanks to these contributors for their code commits. Please report an unintended - [xingfudeshi](https://github.com/xingfudeshi) - [wjwang00](https://github.com/wjwang00) - [YongGoose](https://github.com/YongGoose) +- [JisoLya](https://github.com/JisoLya) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 8fe36aa33e..af54100470 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -59,7 +59,7 @@ - [[#7287](https://github.com/apache/incubator-seata/pull/7287)] 重构了 CodeTest 中的 testGetErrorMsgWithValidCodeReturnsExpectedMsg 测试,以简化并使用参数化单元测试。 - [[#7294](https://github.com/apache/incubator-seata/pull/7294)] 重构了 SqlServerInsertRecognizerTest 中的 testGetInsertParamsValue 测试,通过拆分并使用参数化单元测试进行改进 - [[#7295](https://github.com/apache/incubator-seata/pull/7295)] 重构了 StringUtilsTest 中的 3 个测试,改为使用参数化单元测试 - +- [[#7205](https://github.com/apache/incubator-seata/issues/7205)] 为 namingserver module 添加单元测试 ### refactor: - [[#7315](https://github.com/apache/incubator-seata/pull/7315)] 重构日志测试,使用ListAppender实现更准确高效的日志捕获 @@ -79,5 +79,6 @@ - [xingfudeshi](https://github.com/xingfudeshi) - [wjwang00](https://github.com/wjwang00) - [YongGoose](https://github.com/YongGoose) +- [JisoLya](https://github.com/JisoLya) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/ClusterWatcherManagerTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/ClusterWatcherManagerTest.java new file mode 100644 index 0000000000..f0844aa13b --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/ClusterWatcherManagerTest.java @@ -0,0 +1,199 @@ +/* + * 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.seata.namingserver; + + +import org.apache.seata.namingserver.listener.ClusterChangeEvent; +import org.apache.seata.namingserver.listener.Watcher; +import org.apache.seata.namingserver.manager.ClusterWatcherManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.util.ReflectionTestUtils; + +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +@SpringBootTest +public class ClusterWatcherManagerTest { + + private ClusterWatcherManager clusterWatcherManager; + + @Mock + private AsyncContext asyncContext; + + @Mock + private HttpServletResponse response; + + @Mock + private HttpServletRequest request; + + private final String TEST_GROUP = "testGroup"; + private final int TEST_TIMEOUT = 5000; + private final Long TEST_TERM = 1000L; + private final String TEST_CLIENT_ENDPOINT = "127.0.0.1"; + + @BeforeEach + void setUp() { + clusterWatcherManager = new ClusterWatcherManager(); + Mockito.when(asyncContext.getResponse()).thenReturn(response); + Mockito.when(asyncContext.getRequest()).thenReturn(request); + Mockito.when(request.getRemoteAddr()).thenReturn(TEST_CLIENT_ENDPOINT); + + Map<String, Queue<Watcher<?>>> watchers = (Map<String, Queue<Watcher<?>>>) ReflectionTestUtils.getField(clusterWatcherManager, "WATCHERS"); + Map<String, Long> groupUpdateTime = (Map<String, Long>) ReflectionTestUtils.getField(clusterWatcherManager, "GROUP_UPDATE_TIME"); + + watchers.clear(); + groupUpdateTime.clear(); + } + + @Test + void testInit() { + clusterWatcherManager.init(); + ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = (ScheduledThreadPoolExecutor) ReflectionTestUtils + .getField(clusterWatcherManager, "scheduledThreadPoolExecutor"); + + assertNotNull(scheduledThreadPoolExecutor); + assertFalse(scheduledThreadPoolExecutor.isShutdown()); + } + + @Test + void testRegistryNewWatcher() { + Watcher<AsyncContext> watcher = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, TEST_CLIENT_ENDPOINT); + clusterWatcherManager.registryWatcher(watcher); + + Map<String, Queue<Watcher<?>>> watchers = (Map<String, Queue<Watcher<?>>>) ReflectionTestUtils.getField(clusterWatcherManager, "WATCHERS"); + + assertNotNull(watchers); + assertTrue(watchers.containsKey(TEST_GROUP)); + assertEquals(1, watchers.get(TEST_GROUP).size()); + assertFalse(watcher.isDone()); + } + + @Test + void testRegistryWatcherOldTerm() { + Map<String, Long> groupUpdateTime = (Map<String, Long>) ReflectionTestUtils.getField(clusterWatcherManager, "GROUP_UPDATE_TIME"); + groupUpdateTime.put(TEST_GROUP, TEST_TERM + 10); + + Watcher<AsyncContext> watcher = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, TEST_CLIENT_ENDPOINT); + clusterWatcherManager.registryWatcher(watcher); + + Mockito.verify(response).setStatus(HttpServletResponse.SC_OK); + Mockito.verify(asyncContext).complete(); + assertTrue(watcher.isDone()); + + Map<String, Queue<Watcher<?>>> watchers = (Map<String, Queue<Watcher<?>>>) ReflectionTestUtils.getField(clusterWatcherManager, "WATCHERS"); + + assertFalse(watchers.containsKey(TEST_GROUP)); + assertNull(watchers.get(TEST_GROUP)); + } + + @Test + void testOnEventChange() { + Watcher<AsyncContext> watcher = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, TEST_CLIENT_ENDPOINT); + clusterWatcherManager.registryWatcher(watcher); + Map<String, Queue<Watcher<?>>> watchers = (Map<String, Queue<Watcher<?>>>) ReflectionTestUtils.getField(clusterWatcherManager, "WATCHERS"); + Map<String, Long> updateTime = (Map<String, Long>) ReflectionTestUtils.getField(clusterWatcherManager, "GROUP_UPDATE_TIME"); + + assertNotNull(watchers); + assertNotNull(updateTime); + + ClusterChangeEvent zeroTermEvent = new ClusterChangeEvent(this, TEST_GROUP, 0); + clusterWatcherManager.onChangeEvent(zeroTermEvent); + + assertEquals(0, updateTime.size()); + assertFalse(watcher.isDone()); + assertTrue(watchers.containsKey(TEST_GROUP)); + assertNotNull(watchers.get(TEST_GROUP)); + assertEquals(1, watchers.get(TEST_GROUP).size()); + + ClusterChangeEvent event = new ClusterChangeEvent(this, TEST_GROUP, TEST_TERM + 1); + clusterWatcherManager.onChangeEvent(event); + + Mockito.verify(response).setStatus(HttpServletResponse.SC_OK); + Mockito.verify(asyncContext).complete(); + + assertEquals(1, updateTime.size()); + assertEquals(TEST_TERM + 1, updateTime.get(TEST_GROUP)); + assertTrue(watcher.isDone()); + assertFalse(watchers.containsKey(TEST_GROUP)); + assertNull(watchers.get(TEST_GROUP)); + } + + @Test + void testGetWatcherIpList() { + Watcher<AsyncContext> watcher1 = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, "127.0.0.1"); + Watcher<AsyncContext> watcher2 = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, "127.0.0.1"); + Watcher<AsyncContext> watcher3 = new Watcher<>(TEST_GROUP, asyncContext, TEST_TIMEOUT, TEST_TERM, "192.168.1.1"); + + clusterWatcherManager.registryWatcher(watcher1); + clusterWatcherManager.registryWatcher(watcher2); + clusterWatcherManager.registryWatcher(watcher3); + List<String> watcherIpList = clusterWatcherManager.getWatcherIpList(TEST_GROUP); + + assertNotNull(watcherIpList); + assertEquals(2, watcherIpList.size()); + assertTrue(watcherIpList.contains("127.0.0.1")); + assertTrue(watcherIpList.contains("192.168.1.1")); + } + + @Test + void testGetWatchVGroupList() { + Watcher<AsyncContext> watcher1 = new Watcher<>("VGroup1", asyncContext, TEST_TIMEOUT, TEST_TERM, "127.0.0.1"); + Watcher<AsyncContext> watcher2 = new Watcher<>("VGroup1", asyncContext, TEST_TIMEOUT, TEST_TERM, "127.0.0.2"); + Watcher<AsyncContext> watcher3 = new Watcher<>("VGroup2", asyncContext, TEST_TIMEOUT, TEST_TERM, "192.168.1.1"); + + clusterWatcherManager.registryWatcher(watcher1); + clusterWatcherManager.registryWatcher(watcher2); + clusterWatcherManager.registryWatcher(watcher3); + + List<String> watchVGroupList = clusterWatcherManager.getWatchVGroupList(); + + assertNotNull(watchVGroupList); + assertEquals(2, watchVGroupList.size()); + assertTrue(watchVGroupList.contains("VGroup1")); + assertTrue(watchVGroupList.contains("VGroup2")); + } + + @Test + void testGetTermByvGroup() { + Map<String, Long> groupUpdateTime = (Map<String, Long>) ReflectionTestUtils.getField(clusterWatcherManager, "GROUP_UPDATE_TIME"); + + assertNotNull(groupUpdateTime); + + groupUpdateTime.put(TEST_GROUP, TEST_TERM); + Long term1 = clusterWatcherManager.getTermByvGroup(TEST_GROUP); + Long term2 = clusterWatcherManager.getTermByvGroup("NotExist"); + + assertEquals(TEST_TERM, term1); + assertEquals(0L, term2); + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/NamingControllerTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/NamingControllerTest.java index eb8666b383..f9468252ce 100644 --- a/namingserver/src/test/java/org/apache/seata/namingserver/NamingControllerTest.java +++ b/namingserver/src/test/java/org/apache/seata/namingserver/NamingControllerTest.java @@ -21,14 +21,19 @@ import org.apache.seata.common.metadata.Node; import org.apache.seata.common.metadata.namingserver.MetaResponse; import org.apache.seata.common.metadata.namingserver.NamingServerNode; import org.apache.seata.common.metadata.namingserver.Unit; +import org.apache.seata.common.result.Result; import org.apache.seata.namingserver.controller.NamingController; +import org.apache.seata.namingserver.manager.NamingManager; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -62,8 +67,8 @@ class NamingControllerTest { node.setTransaction(new Node.Endpoint("127.0.0.1", 8091, "netty")); node.setControl(new Node.Endpoint("127.0.0.1", 7091, "http")); Map<String, Object> meatadata = node.getMetadata(); - Map<String,Object> vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); meatadata.put(CONSTANT_GROUP, vGroups); namingController.registerInstance(namespace, clusterName, unitName, node); namingController.changeGroup(namespace, clusterName, unitName, vGroup); @@ -93,17 +98,17 @@ class NamingControllerTest { node.setTransaction(new Node.Endpoint("127.0.0.1", 8092, "netty")); node.setControl(new Node.Endpoint("127.0.0.1", 7092, "http")); Map<String, Object> meatadata = node.getMetadata(); - Map<String,Object> vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); meatadata.put(CONSTANT_GROUP, vGroups); namingController.registerInstance(namespace, clusterName, unitName, node); NamingServerNode node2 = new NamingServerNode(); node2.setTransaction(new Node.Endpoint("127.0.0.1", 8093, "netty")); node2.setControl(new Node.Endpoint("127.0.0.1", 7093, "http")); Map<String, Object> meatadata2 = node2.getMetadata(); - Map<String,Object> vGroups2 = new HashMap<>(); + Map<String, Object> vGroups2 = new HashMap<>(); String unitName2 = UUID.randomUUID().toString(); - vGroups2.put(UUID.randomUUID().toString(),unitName2); + vGroups2.put(UUID.randomUUID().toString(), unitName2); meatadata2.put(CONSTANT_GROUP, vGroups2); namingController.registerInstance(namespace, UUID.randomUUID().toString(), unitName2, node2); MetaResponse metaResponse = namingController.discovery(vGroup, namespace); @@ -136,8 +141,8 @@ class NamingControllerTest { node.setTransaction(new Node.Endpoint("127.0.0.1", 8094, "netty")); node.setControl(new Node.Endpoint("127.0.0.1", 7094, "http")); Map<String, Object> meatadata = node.getMetadata(); - Map<String,Object> vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); meatadata.put(CONSTANT_GROUP, vGroups); namingController.registerInstance(namespace, clusterName, unitName, node); //namingController.changeGroup(namespace, clusterName, vGroup, vGroup); @@ -172,15 +177,15 @@ class NamingControllerTest { node.setTransaction(new Node.Endpoint("127.0.0.1", 8095, "netty")); node.setControl(new Node.Endpoint("127.0.0.1", 7095, "http")); Map<String, Object> meatadata = node.getMetadata(); - Map<String,Object> vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); meatadata.put(CONSTANT_GROUP, vGroups); NamingServerNode node2 = new NamingServerNode(); String unitName2 = String.valueOf(UUID.randomUUID()); node2.setTransaction(new Node.Endpoint("127.0.0.1", 8096, "netty")); node2.setControl(new Node.Endpoint("127.0.0.1", 7096, "http")); vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName2); + vGroups.put(vGroup, unitName2); node2.getMetadata().put(CONSTANT_GROUP, vGroups); namingController.registerInstance(namespace, clusterName, unitName, node); namingController.registerInstance(namespace, clusterName, unitName2, node2); @@ -216,20 +221,20 @@ class NamingControllerTest { node.setTransaction(new Node.Endpoint("127.0.0.1", 8097, "netty")); node.setControl(new Node.Endpoint("127.0.0.1", 7097, "http")); Map<String, Object> meatadata = node.getMetadata(); - Map<String,Object> vGroups = new HashMap<>(); - vGroups.put(vGroup,unitName); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); meatadata.put(CONSTANT_GROUP, vGroups); namingController.registerInstance(namespace, clusterName, unitName, node); NamingServerNode node2 = new NamingServerNode(); node2.setTransaction(new Node.Endpoint("127.0.0.1", 8098, "netty")); node2.setControl(new Node.Endpoint("127.0.0.1", 7098, "http")); Map<String, Object> meatadata2 = node2.getMetadata(); - Map<String,Object> vGroups2 = new HashMap<>(); + Map<String, Object> vGroups2 = new HashMap<>(); String unitName2 = UUID.randomUUID().toString(); - vGroups2.put(vGroup,unitName2); + vGroups2.put(vGroup, unitName2); meatadata2.put(CONSTANT_GROUP, vGroups2); namingController.registerInstance(namespace, clusterName, unitName2, node2); - Thread thread = new Thread(()->{ + Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { TimeUnit.SECONDS.sleep(5); @@ -262,4 +267,133 @@ class NamingControllerTest { assertEquals(8097, node1.getTransaction().getPort()); } + @Test + void testRegisterInstanceReturnMessageSuccess() { + String clusterName = "cluster6"; + String namespace = "public6"; + String vGroup = "testRegisterInstanceReturnMessage"; + String unitName = String.valueOf(UUID.randomUUID()); + NamingServerNode node = new NamingServerNode(); + node.setTransaction(new Node.Endpoint("127.0.0.1", 8094, "netty")); + node.setControl(new Node.Endpoint("127.0.0.1", 7094, "http")); + Map<String, Object> meatadata = node.getMetadata(); + Map<String, Object> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + meatadata.put(CONSTANT_GROUP, vGroups); + Result<String> result = namingController.registerInstance(namespace, clusterName, unitName, node); + assertNotNull(result); + assertEquals("200", result.getCode()); + assertEquals("node has registered successfully!", result.getMessage()); + namingController.unregisterInstance(namespace, clusterName, unitName, node); + } + + @Test + void testRegisterInstanceReturnMessageFailure() { + String clusterName = "cluster7"; + String namespace = "public7"; + String unitName = String.valueOf(UUID.randomUUID()); + NamingServerNode invalidNode = new NamingServerNode(); + Result<String> result = namingController.registerInstance(namespace, clusterName, unitName, invalidNode); + assertNotNull(result); + assertEquals("500", result.getCode()); + assertEquals("node registered unsuccessfully!", result.getMessage()); + } + + @Test + void testBatchRegisterInstanceMessageSuccess() { + String clusterName = "cluster8"; + String namespace = "public8"; + String unitName = String.valueOf(UUID.randomUUID()); + NamingServerNode node1 = new NamingServerNode(); + node1.setTransaction(new Node.Endpoint("127.0.0.1", 8097, "netty")); + node1.setControl(new Node.Endpoint("127.0.0.1", 7097, "http")); + node1.setUnit(unitName); + NamingServerNode node2 = new NamingServerNode(); + node2.setTransaction(new Node.Endpoint("127.0.0.1", 8098, "netty")); + node2.setControl(new Node.Endpoint("127.0.0.1", 7098, "http")); + node2.setUnit(unitName); + ArrayList<NamingServerNode> nodeList = new ArrayList<>(); + nodeList.add(node1); + nodeList.add(node2); + Result<String> result = namingController.batchRegisterInstance(namespace, clusterName, nodeList); + assertNotNull(result); + assertEquals("200", result.getCode()); + assertEquals("node has registered successfully!", result.getMessage()); + } + + @Test + void testBatchRegisterInstanceMessageFailure() { + String clusterName = "cluster9"; + String namespace = "public9"; + String unitName = String.valueOf(UUID.randomUUID()); + NamingServerNode node1 = new NamingServerNode(); + node1.setTransaction(new Node.Endpoint("127.0.0.1", 8097, "netty")); + node1.setControl(new Node.Endpoint("127.0.0.1", 7097, "http")); + node1.setUnit(unitName); + NamingServerNode invalidNode = new NamingServerNode(); + ArrayList<NamingServerNode> nodeList = new ArrayList<>(); + nodeList.add(node1); + nodeList.add(invalidNode); + Result<String> result = namingController.batchRegisterInstance(namespace, clusterName, nodeList); + assertNotNull(result); + assertEquals("500", result.getCode()); + assertEquals("node registered unsuccessfully!", result.getMessage()); + } + + @Test + void testAddGroupMessageSuccess() { + String namespace = "public10"; + String clusterName = "cluster10"; + String unitName = String.valueOf(UUID.randomUUID()); + String vGroup = "testAddGroupMessageSuccess"; + NamingManager mockNamingManager = Mockito.mock(NamingManager.class); + Result<String> expectedResult = new Result<>("200", "add vGroup successfully!"); + Mockito.when(mockNamingManager.createGroup(namespace, vGroup, clusterName, unitName)).thenReturn(expectedResult); + try { + Field field = NamingController.class.getDeclaredField("namingManager"); + field.setAccessible(true); + NamingManager originalNamingManager = (NamingManager) field.get(namingController); + field.set(namingController, mockNamingManager); + try { + Result<String> result = namingController.addGroup(namespace, clusterName, unitName, vGroup); + assertNotNull(result); + assertEquals("200", result.getCode()); + assertEquals("change vGroup " + vGroup + "to cluster " + clusterName + " successfully!", result.getMessage()); + Mockito.verify(mockNamingManager).createGroup(namespace, vGroup, clusterName, unitName); + } finally { + field.set(namingController, originalNamingManager); + } + } catch (Exception e) { + fail("Test failed due to exception: " + e.getMessage()); + } + } + + @Test + void testAddGroupMessageFailure() { + String namespace = "public11"; + String clusterName = "cluster11"; + String unitName = String.valueOf(UUID.randomUUID()); + String vGroup = "testAddGroupMessageFailure"; + NamingManager mockNamingManager = Mockito.mock(NamingManager.class); + Result<String> expectedResult = new Result<>("500", "add vGroup in new cluster failed"); + Mockito.when(mockNamingManager.createGroup(namespace, vGroup, clusterName, unitName)).thenReturn(expectedResult); + try { + Field field = NamingController.class.getDeclaredField("namingManager"); + field.setAccessible(true); + NamingManager originalNamingManager = (NamingManager) field.get(namingController); + field.set(namingController, mockNamingManager); + try { + Result<String> result = namingController.addGroup(namespace, clusterName, unitName, vGroup); + assertNotNull(result); + assertEquals("500", result.getCode()); + assertEquals("add vGroup in new cluster failed", result.getMessage()); + Mockito.verify(mockNamingManager).createGroup(namespace, vGroup, clusterName, unitName); + } finally { + field.set(namingController, originalNamingManager); + } + } catch (Exception e) { + fail("Test failed due to exception: " + e.getMessage()); + } + } + } \ No newline at end of file diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/NamingEntityTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/NamingEntityTest.java new file mode 100644 index 0000000000..08c988fc5f --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/NamingEntityTest.java @@ -0,0 +1,206 @@ +/* + * 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.seata.namingserver; + +import org.apache.seata.common.metadata.namingserver.Unit; +import org.apache.seata.namingserver.entity.bo.ClusterBO; +import org.apache.seata.namingserver.entity.pojo.ClusterData; +import org.apache.seata.namingserver.entity.vo.NamespaceVO; +import org.apache.seata.namingserver.entity.vo.monitor.ClusterVO; +import org.apache.seata.namingserver.entity.vo.monitor.WatcherVO; +import org.apache.seata.namingserver.listener.Watcher; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +@SpringBootTest +class NamingEntityTest { + + @Test + void testClusterBO() { + HashSet<String> unitNames1 = new HashSet<>(); + unitNames1.add("testClusterBO1"); + unitNames1.add("testClusterBO2"); + ClusterBO clusterBO = new ClusterBO(unitNames1); + assertNotNull(clusterBO); + Set<String> actualUnitNames = clusterBO.getUnitNames(); + assertEquals(unitNames1.size(), actualUnitNames.size()); + unitNames1.forEach(unit -> assertTrue(actualUnitNames.contains(unit))); + actualUnitNames.forEach(unit -> assertTrue(unitNames1.contains(unit))); + + HashSet<String> unitNames2 = new HashSet<>(); + unitNames2.add("testClusterBO3"); + clusterBO.setUnitNames(unitNames2); + Set<String> afterSetter = clusterBO.getUnitNames(); + assertEquals(unitNames2.size(), afterSetter.size()); + unitNames2.forEach(unit -> assertTrue(afterSetter.contains(unit))); + afterSetter.forEach(unit -> assertTrue(unitNames2.contains(unit))); + } + + @Test + void testNamespaceVO() { + NamespaceVO namespaceVO = new NamespaceVO(); + assertNotNull(namespaceVO); + assertNotNull(namespaceVO.getClusters()); + assertNotNull(namespaceVO.getVgroups()); + + ArrayList<String> vGroups = new ArrayList<>(); + ArrayList<String> clusters = new ArrayList<>(); + vGroups.add("testVGroup1"); + vGroups.add("testVGroup2"); + clusters.add("testCluster1"); + clusters.add("testCluster2"); + + namespaceVO.setVgroups(vGroups); + namespaceVO.setClusters(clusters); + + assertEquals(namespaceVO.getVgroups().size(), vGroups.size()); + assertEquals(namespaceVO.getClusters().size(), clusters.size()); + vGroups.forEach(unit -> assertTrue(namespaceVO.getVgroups().contains(unit))); + clusters.forEach(unit -> assertTrue(namespaceVO.getClusters().contains(unit))); + } + + @Test + void testDefaultConstructorClusterVO() { + ClusterVO clusterVO = new ClusterVO(); + assertNotNull(clusterVO); + assertNotNull(clusterVO.getUnitData()); + assertNotNull(clusterVO.getvGroupMapping()); + assertEquals(0, clusterVO.getUnitData().size()); + assertEquals(0, clusterVO.getvGroupMapping().size()); + } + + @Test + void testParamConstructorClusterVO() { + String clusterName = "testCluster"; + String clusterType = "testClusterType"; + ArrayList<Unit> unitData = new ArrayList<>(); + ClusterVO clusterVO = new ClusterVO(clusterName, clusterType, unitData); + + assertNotNull(clusterVO); + assertNotNull(clusterVO.getUnitData()); + assertNotNull(clusterVO.getvGroupMapping()); + assertEquals(0, clusterVO.getUnitData().size()); + assertEquals(0, clusterVO.getvGroupMapping().size()); + assertEquals(clusterName, clusterVO.getClusterName()); + assertEquals(clusterType, clusterVO.getClusterType()); + } + + @Test + void testSetvGroupMappingClusterVO() { + ClusterVO clusterVO = new ClusterVO(); + assertNotNull(clusterVO); + assertNotNull(clusterVO.getvGroupMapping()); + assertEquals(0, clusterVO.getvGroupMapping().size()); + + ArrayList<String> vGroupMapping = new ArrayList<>(); + vGroupMapping.add("testVGroupMapping1"); + clusterVO.setvGroupMapping(vGroupMapping); + assertEquals(1, clusterVO.getvGroupMapping().size()); + vGroupMapping.forEach(unit -> assertTrue(clusterVO.getvGroupMapping().contains(unit))); + } + + @Test + void testConvertFromClusterData() { + ClusterData clusterData = new ClusterData(); + clusterData.setClusterName("testCluster"); + clusterData.setClusterType("testClusterType"); + clusterData.getUnitData().put("test", new Unit()); + + ClusterVO clusterVO = ClusterVO.convertFromClusterData(clusterData); + + assertNotNull(clusterVO); + assertNotNull(clusterVO.getUnitData()); + assertNotNull(clusterVO.getvGroupMapping()); + assertEquals(1, clusterVO.getUnitData().size()); + assertEquals(0, clusterVO.getvGroupMapping().size()); + assertEquals("testCluster", clusterVO.getClusterName()); + assertEquals("testClusterType", clusterVO.getClusterType()); + } + + @Test + void testAddMapping() { + ClusterVO clusterVO = new ClusterVO(); + clusterVO.getvGroupMapping().add("testVGroupMapping1"); + + clusterVO.addMapping("testVGroupMapping1"); + assertEquals(1, clusterVO.getvGroupMapping().size()); + + clusterVO.addMapping("testVGroupMapping2"); + assertEquals(2, clusterVO.getvGroupMapping().size()); + } + + @Test + void testWatchVO() { + WatcherVO watcherVO1 = new WatcherVO(); + ArrayList<String> watcherIP1 = new ArrayList<>(); + watcherVO1.setvGroup("testWatcher1"); + watcherVO1.setWatcherIp(watcherIP1); + assertNotNull(watcherVO1); + assertNotNull(watcherVO1.getWatcherIp()); + assertEquals("testWatcher1", watcherVO1.getvGroup()); + assertEquals(0, watcherVO1.getWatcherIp().size()); + + String vGroup = "testWatch"; + ArrayList<String> watcherIP2 = new ArrayList<>(); + watcherIP2.add("127.0.0.1"); + WatcherVO watcherVO2 = new WatcherVO(vGroup, watcherIP2); + assertNotNull(watcherVO2); + assertNotNull(watcherVO2.getWatcherIp()); + assertEquals("testWatch", watcherVO2.getvGroup()); + assertEquals(1, watcherVO2.getWatcherIp().size()); + watcherIP2.forEach(unit -> assertTrue(watcherVO2.getWatcherIp().contains(unit))); + } + + @Test + void testWatcher() { + String group = "testWatcher"; + String asyncContext = "testAsyncContext"; + int timeout = 10; + long term = new Random().nextLong(); + String clientEndpoint = "127.0.0.1"; + Watcher<String> watcher = new Watcher<>(group, asyncContext, timeout, term, clientEndpoint); + + assertNotNull(watcher); + assertEquals(group, watcher.getGroup()); + assertEquals(asyncContext, watcher.getAsyncContext()); + assertEquals(term, watcher.getTerm()); + assertEquals(clientEndpoint, watcher.getClientEndpoint()); + assertEquals("http", watcher.getProtocol()); + assertFalse(watcher.isDone()); + + watcher.setTerm(100); + watcher.setAsyncContext("newAsyncContext"); + watcher.setClientEndpoint("127.0.0.2"); + watcher.setProtocol("gRPC"); + watcher.setDone(true); + assertEquals(100, watcher.getTerm()); + assertEquals("newAsyncContext", watcher.getAsyncContext()); + assertEquals("127.0.0.2", watcher.getClientEndpoint()); + assertEquals("gRPC", watcher.getProtocol()); + assertTrue(watcher.isDone()); + } +} diff --git a/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java b/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java new file mode 100644 index 0000000000..188201b544 --- /dev/null +++ b/namingserver/src/test/java/org/apache/seata/namingserver/NamingManagerTest.java @@ -0,0 +1,354 @@ +/* + * 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.seata.namingserver; + +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.seata.common.metadata.Cluster; +import org.apache.seata.common.metadata.ClusterRole; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.metadata.namingserver.NamingServerNode; +import org.apache.seata.common.metadata.namingserver.Unit; +import org.apache.seata.common.result.Result; +import org.apache.seata.common.util.HttpClientUtil; +import org.apache.seata.namingserver.entity.vo.monitor.ClusterVO; +import org.apache.seata.namingserver.listener.ClusterChangeEvent; +import org.apache.seata.namingserver.manager.NamingManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.UUID; +import java.util.Arrays; + +import static org.apache.seata.common.NamingServerConstants.CONSTANT_GROUP; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyInt; + +@SpringBootTest +class NamingManagerTest { + + private NamingManager namingManager; + + @Mock + private ApplicationContext applicationContext; + + @Mock + private CloseableHttpResponse httpResponse; + + @Mock + private StatusLine statusLine; + + private MockedStatic<HttpClientUtil> mockedHttpClientUtil; + + @BeforeEach + void setUp() { + namingManager = new NamingManager(); + ReflectionTestUtils.setField(namingManager, "applicationContext", applicationContext); + ReflectionTestUtils.setField(namingManager, "heartbeatTimeThreshold", 500000); + ReflectionTestUtils.setField(namingManager, "heartbeatCheckTimePeriod", 10000000); + + Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); + mockedHttpClientUtil = Mockito.mockStatic(HttpClientUtil.class); + mockedHttpClientUtil.when(() -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt())).thenReturn(httpResponse); + + namingManager.init(); + } + + private NamingServerNode createTestNode(String host, int port, String unitName) { + NamingServerNode node = new NamingServerNode(); + node.setTransaction(new Node.Endpoint(host, port, "netty")); + node.setControl(new Node.Endpoint(host, port + 1000, "http")); + node.setUnit(unitName); + node.setRole(ClusterRole.LEADER); + node.setTerm(1L); + Map<String, Object> metadata = new HashMap<>(); + metadata.put("cluster-type", "default"); + node.setMetadata(metadata); + return node; + } + + @AfterEach + void tearDown() { + if (mockedHttpClientUtil != null) { + mockedHttpClientUtil.close(); + } + } + + @Test + void testRegisterInstance() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + boolean result = namingManager.registerInstance(node, namespace, clusterName, unitName); + + assertTrue(result); + + List<Node> instances = namingManager.getInstances(namespace, clusterName); + assertEquals(1, instances.size()); + assertEquals("127.0.0.1", instances.get(0).getTransaction().getHost()); + assertEquals(8080, instances.get(0).getTransaction().getPort()); + } + + @Test + void testRegisterInstances() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + + NamingServerNode node1 = createTestNode("127.0.0.1", 8001, unitName); + NamingServerNode node2 = createTestNode("127.0.0.1", 8002, unitName); + List<NamingServerNode> nodeList = Arrays.asList(node1, node2); + + boolean result = namingManager.registerInstances(nodeList, namespace, clusterName); + + assertTrue(result); + assertEquals(2, namingManager.getInstances(namespace, clusterName).size()); + } + + @Test + void testUnregisterInstance() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + List<Node> instances = namingManager.getInstances(namespace, clusterName); + assertEquals(1, instances.size()); + + boolean result = namingManager.unregisterInstance(namespace, clusterName, unitName, node); + + assertTrue(result); + assertTrue(namingManager.getInstances(namespace, clusterName).isEmpty()); + Mockito.verify(applicationContext, Mockito.times(2)).publishEvent(any(ClusterChangeEvent.class)); + } + + @Test + void testGetClusterListByVgroup() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + List<Cluster> clusterListByVgroup = namingManager.getClusterListByVgroup(vGroup, namespace); + + assertNotNull(clusterListByVgroup); + assertEquals(1, clusterListByVgroup.size()); + assertEquals(clusterName, clusterListByVgroup.get(0).getClusterName()); + + List<Cluster> notExist = namingManager.getClusterListByVgroup("NotExist-vGroup", namespace); + assertEquals(0, notExist.size()); + } + + @Test + void testInstanceHeartBeatCheck() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + List<Node> instances = namingManager.getInstances(namespace, clusterName); + assertEquals(1, instances.size()); + + ReflectionTestUtils.setField(namingManager, "heartbeatTimeThreshold", 10); + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + namingManager.instanceHeartBeatCheck(); + + List<Node> afterHeartBeat = namingManager.getInstances(namespace, clusterName); + assertEquals(0, afterHeartBeat.size()); + Mockito.verify(applicationContext, Mockito.times(2)).publishEvent(any(ClusterChangeEvent.class)); + } + + @Test + void testCreateGroup() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + Mockito.when(statusLine.getStatusCode()).thenReturn(200); + Result<String> result = namingManager.createGroup(namespace, vGroup, clusterName, unitName); + assertTrue(result.isSuccess()); + assertEquals("200", result.getCode()); + assertEquals("add vGroup successfully!", result.getMessage()); + + mockedHttpClientUtil.verify(() -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt()), Mockito.times(1)); + } + + @Test + void testCreateGroupNoInstance() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + namingManager.registerInstance(null, namespace, clusterName, unitName); + Result<String> result = namingManager.createGroup(namespace, vGroup, clusterName, unitName); + + assertFalse(result.isSuccess()); + assertEquals("301", result.getCode()); + assertEquals("no instance in cluster:" + clusterName, result.getMessage()); + } + + @Test + void testAddGroup() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8080, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + boolean result = namingManager.addGroup(namespace, vGroup, clusterName, unitName); + assertTrue(result); + + boolean result1 = namingManager.addGroup(namespace, vGroup, clusterName, unitName); + assertFalse(result1); + } + + @Test + void testRemoveGroup() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8000, unitName); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + Unit unit = new Unit(); + unit.setUnitName(unitName); + List<NamingServerNode> nodeList = new ArrayList<>(); + nodeList.add(node); + unit.setNamingInstanceList(nodeList); + + Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); + Mockito.when(statusLine.getStatusCode()).thenReturn(200); + + + mockedHttpClientUtil.when(() -> HttpClientUtil.doGet(anyString(), anyMap(), anyMap(), anyInt())) + .thenReturn(httpResponse); + + Result<String> result = namingManager.removeGroup(unit, vGroup, clusterName, namespace, unitName); + + assertTrue(result.isSuccess()); + assertEquals("200", result.getCode()); + assertEquals("remove group in old cluster successfully!", result.getMessage()); + + mockedHttpClientUtil.verify(() -> HttpClientUtil.doGet( + anyString(), anyMap(), anyMap(), anyInt()), Mockito.times(1)); + + } + + @Test + void testMonitorCluster() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + List<ClusterVO> emptyResult = namingManager.monitorCluster("empty-namespace"); + assertTrue(emptyResult.isEmpty()); + + NamingServerNode node = createTestNode("127.0.0.1", 8001, unitName); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + List<ClusterVO> resultWithoutMapping = namingManager.monitorCluster(namespace); + assertFalse(resultWithoutMapping.isEmpty()); + assertEquals(1, resultWithoutMapping.size()); + ClusterVO clusterVO = resultWithoutMapping.get(0); + assertEquals(clusterName, clusterVO.getClusterName()); + assertTrue(clusterVO.getvGroupMapping().isEmpty()); + + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + List<ClusterVO> resultWithMapping = namingManager.monitorCluster(namespace); + assertFalse(resultWithMapping.get(0).getvGroupMapping().isEmpty()); + assertTrue(resultWithMapping.get(0).getvGroupMapping().contains(vGroup)); + } + + @Test + void testNotifyClusterChange() { + String namespace = "test-namespace"; + String clusterName = "test-cluster"; + String unitName = UUID.randomUUID().toString(); + String vGroup = "test-vGroup"; + + NamingServerNode node = createTestNode("127.0.0.1", 8000, unitName); + Map<String, String> vGroups = new HashMap<>(); + vGroups.put(vGroup, unitName); + node.getMetadata().put(CONSTANT_GROUP, vGroups); + namingManager.registerInstance(node, namespace, clusterName, unitName); + + namingManager.notifyClusterChange(vGroup, namespace, clusterName, unitName, 1000L); + + Mockito.verify(applicationContext, Mockito.times(2)).publishEvent(any(ClusterChangeEvent.class)); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@seata.apache.org For additional commands, e-mail: notifications-h...@seata.apache.org