This is an automated email from the ASF dual-hosted git repository.
Aias00 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 1e10196da9 feat(admin): opt in to compact WebSocket discovery payloads
(#7883)
1e10196da9 is described below
commit 1e10196da99c1e6d3e2f93df83c515d79b25e1ad
Author: cloudbase-ai <[email protected]>
AuthorDate: Fri Oct 23 11:29:20 2026 +0800
feat(admin): opt in to compact WebSocket discovery payloads (#7883)
Signed-off-by: cloudbase-ai <[email protected]>
Co-authored-by: cloudbase-ai
<[email protected]>
---
shenyu-admin/README.md | 19 +++
.../admin/config/WebSocketSyncConfiguration.java | 5 +-
.../config/properties/WebsocketSyncProperties.java | 23 ++++
.../websocket/WebsocketDataChangedListener.java | 29 +++++
.../admin/model/dto/DiscoverySyncPayload.java | 70 ++++++++++
shenyu-admin/src/main/resources/application.yml | 2 +
.../config/WebSocketSyncConfigurationTest.java | 3 +-
.../properties/WebsocketSyncPropertiesTest.java | 5 +-
.../WebsocketDataChangedListenerTest.java | 143 +++++++++++++++++++++
9 files changed, 295 insertions(+), 4 deletions(-)
diff --git a/shenyu-admin/README.md b/shenyu-admin/README.md
index 3f9ba6958e..f878ac9d2e 100644
--- a/shenyu-admin/README.md
+++ b/shenyu-admin/README.md
@@ -1,3 +1,22 @@
# ShenYu Admin API Document
You can check http://localhost:9095/swagger-ui.html to check all restful api
and models.
+
+## Compact Discovery messages (WebSocket only)
+
+`shenyu.sync.websocket.compact-discovery-payload` defaults to `false`.
Enabling it projects
+Discovery messages at the Admin-to-gateway serialization boundary. It omits
the outer
+`discoveryHandlerId` and each upstream's `id`, `dateUpdated`,
`discoveryHandlerId`, and
+repeated `namespaceId`. The enclosing namespace and selector identities,
creation timestamp
+(for warmup), protocol, URL, manual status, weight, and opaque `props` are
preserved.
+Empty upstream lists retain their clearing semantics. This is a full runtime
item, not a patch.
+
+Audit every gateway consuming that Admin before enabling it, including old
versions,
+external plugins and WASM guests: they may use fields that built-in handlers
do not read.
+There is no per-session capability negotiation in this first, opt-in WebSocket
implementation.
+Leave the option disabled for unaudited or mixed fleets. Disable it and
resynchronize/reconnect
+consumers to return to the legacy payload. Configure all Admin nodes
consistently.
+
+This option does not change internal Admin events, cluster forwarding, HTTP
polling,
+registry-backed synchronization, selectors/rules, or the WebSocket envelope
and initial-sync
+completion protocol. No extra JSON parse/delete/serialize pass is used to
build compact messages.
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/WebSocketSyncConfiguration.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/WebSocketSyncConfiguration.java
index b58aea5ba9..af85c7f2f4 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/WebSocketSyncConfiguration.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/WebSocketSyncConfiguration.java
@@ -51,12 +51,13 @@ public class WebSocketSyncConfiguration {
/**
* Config event listener data changed listener.
*
+ * @param properties WebSocket sync properties
* @return the data changed listener
*/
@Bean
@ConditionalOnMissingBean(WebsocketDataChangedListener.class)
- public DataChangedListener websocketDataChangedListener() {
- return new WebsocketDataChangedListener();
+ public DataChangedListener websocketDataChangedListener(final
WebsocketSyncProperties properties) {
+ return new
WebsocketDataChangedListener(properties.isCompactDiscoveryPayload());
}
/**
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/WebsocketSyncProperties.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/WebsocketSyncProperties.java
index 897c8429f6..7261299ca1 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/WebsocketSyncProperties.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/WebsocketSyncProperties.java
@@ -47,8 +47,31 @@ public class WebsocketSyncProperties {
*/
private String token;
+ /**
+ * Omit Discovery persistence fields from gateway messages. Opt in only
after auditing all consumers.
+ */
+ private boolean compactDiscoveryPayload;
+
private final Reconciliation reconciliation = new Reconciliation();
+ /**
+ * Whether Discovery messages use the gateway-only projection.
+ *
+ * @return whether compact messages are enabled
+ */
+ public boolean isCompactDiscoveryPayload() {
+ return compactDiscoveryPayload;
+ }
+
+ /**
+ * Enable the Discovery projection for WebSocket consumers.
+ *
+ * @param compactDiscoveryPayload whether to omit persistence fields
+ */
+ public void setCompactDiscoveryPayload(final boolean
compactDiscoveryPayload) {
+ this.compactDiscoveryPayload = compactDiscoveryPayload;
+ }
+
/**
* Gets the value of enabled.
*
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
index a034c62677..9e2b600265 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListener.java
@@ -22,6 +22,7 @@ import static
org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.admin.listener.DataChangedListener;
+import org.apache.shenyu.admin.model.dto.DiscoverySyncPayload;
import org.apache.shenyu.common.dto.AppAuthData;
import org.apache.shenyu.common.dto.DiscoverySyncData;
import org.apache.shenyu.common.dto.MetaData;
@@ -54,6 +55,24 @@ public class WebsocketDataChangedListener implements
DataChangedListener {
private static final Logger LOG =
LoggerFactory.getLogger(WebsocketDataChangedListener.class);
+ private final boolean compactDiscoveryPayload;
+
+ /**
+ * Create a listener using the legacy payload.
+ */
+ public WebsocketDataChangedListener() {
+ this(false);
+ }
+
+ /**
+ * Create a listener with optional Discovery payload projection.
+ *
+ * @param compactDiscoveryPayload whether to omit Discovery management
fields
+ */
+ public WebsocketDataChangedListener(final boolean compactDiscoveryPayload)
{
+ this.compactDiscoveryPayload = compactDiscoveryPayload;
+ }
+
@Override
public void onPluginChanged(final List<PluginData> pluginDataList, final
DataEventTypeEnum eventType) {
sendByNamespace(pluginDataList, eventType, ConfigGroupEnum.PLUGIN,
PluginData::getNamespaceId);
@@ -142,6 +161,16 @@ public class WebsocketDataChangedListener implements
DataChangedListener {
@Override
public void onDiscoveryUpstreamChanged(final List<DiscoverySyncData>
discoveryUpstreamList, final DataEventTypeEnum eventType) {
+ if (compactDiscoveryPayload &&
CollectionUtils.isNotEmpty(discoveryUpstreamList)) {
+ List<DiscoverySyncPayload> payloads = new
ArrayList<>(discoveryUpstreamList.size());
+ for (DiscoverySyncData data : discoveryUpstreamList) {
+ if (Objects.nonNull(data)) {
+ payloads.add(DiscoverySyncPayload.from(data));
+ }
+ }
+ sendByNamespace(payloads, eventType,
ConfigGroupEnum.DISCOVER_UPSTREAM, DiscoverySyncPayload::namespaceId);
+ return;
+ }
sendByNamespace(discoveryUpstreamList, eventType,
ConfigGroupEnum.DISCOVER_UPSTREAM, DiscoverySyncData::getNamespaceId);
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoverySyncPayload.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoverySyncPayload.java
new file mode 100644
index 0000000000..277c07923f
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/DiscoverySyncPayload.java
@@ -0,0 +1,70 @@
+/*
+ * 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.admin.model.dto;
+
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Gateway-only Discovery projection. Persistence identifiers remain in the
Admin event.
+ *
+ * @param selectorId selector identity
+ * @param pluginName plugin handler identity
+ * @param selectorName selector name used by TCP routing
+ * @param namespaceId namespace identity
+ * @param upstreamDataList runtime upstream configuration
+ */
+public record DiscoverySyncPayload(String selectorId, String pluginName,
String selectorName,
+ String namespaceId, List<UpstreamPayload>
upstreamDataList) {
+
+ /**
+ * Project without mutating the shared event or interpreting
plugin-specific props.
+ *
+ * @param data source Discovery data
+ * @return gateway payload
+ */
+ public static DiscoverySyncPayload from(final DiscoverySyncData data) {
+ List<UpstreamPayload> upstreams =
Objects.isNull(data.getUpstreamDataList()) ? null
+ :
data.getUpstreamDataList().stream().map(UpstreamPayload::from).collect(Collectors.toList());
+ return new DiscoverySyncPayload(data.getSelectorId(),
data.getPluginName(), data.getSelectorName(),
+ data.getNamespaceId(), upstreams);
+ }
+
+ /**
+ * Runtime fields understood by the built-in gateway handlers.
+ *
+ * @param dateCreated creation timestamp used for load-balancer warmup
+ * @param protocol protocol prefix
+ * @param url upstream address
+ * @param status manual health status
+ * @param weight load-balancing weight
+ * @param props opaque plugin metadata
+ */
+ public record UpstreamPayload(Timestamp dateCreated, String protocol,
String url, int status, int weight, String props) {
+
+ private static UpstreamPayload from(final DiscoveryUpstreamData data) {
+ return Objects.isNull(data) ? null : new
UpstreamPayload(data.getDateCreated(), data.getProtocol(),
+ data.getUrl(), data.getStatus(), data.getWeight(),
data.getProps());
+ }
+ }
+}
diff --git a/shenyu-admin/src/main/resources/application.yml
b/shenyu-admin/src/main/resources/application.yml
index c7cf7fe0ef..fc139629a6 100755
--- a/shenyu-admin/src/main/resources/application.yml
+++ b/shenyu-admin/src/main/resources/application.yml
@@ -70,6 +70,8 @@ shenyu:
websocket:
enabled: true
messageMaxSize: 10240
+ # Opt in only after checking built-in and external gateway consumers.
See shenyu-admin/README.md.
+ compact-discovery-payload: false
token: ${SHENYU_SYNC_WEBSOCKET_TOKEN:}
allowOrigins: ws://localhost:9095;ws://localhost:9195;
# Reconciles gateway sessions connected to this admin node with the
shared
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/WebSocketSyncConfigurationTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/WebSocketSyncConfigurationTest.java
index 0ef0be9f58..e1d81217a9 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/WebSocketSyncConfigurationTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/WebSocketSyncConfigurationTest.java
@@ -17,6 +17,7 @@
package org.apache.shenyu.admin.config;
+import org.apache.shenyu.admin.config.properties.WebsocketSyncProperties;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -26,7 +27,7 @@ public class WebSocketSyncConfigurationTest {
@Test
public void testWebsocketDataChangedListener() {
WebSocketSyncConfiguration websocketListener = new
WebSocketSyncConfiguration();
- assertNotNull(websocketListener.websocketDataChangedListener());
+ assertNotNull(websocketListener.websocketDataChangedListener(new
WebsocketSyncProperties()));
}
@Test
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/properties/WebsocketSyncPropertiesTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/properties/WebsocketSyncPropertiesTest.java
index e6b7f844b6..ec86b35626 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/properties/WebsocketSyncPropertiesTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/config/properties/WebsocketSyncPropertiesTest.java
@@ -35,11 +35,13 @@ public final class WebsocketSyncPropertiesTest extends
AbstractConfigurationTest
@Test
public void testWebsocketSyncPropertiesDefaultValue() {
assertThat(new WebsocketSyncProperties().isEnabled(), is(true));
+ Assertions.assertFalse(new
WebsocketSyncProperties().isCompactDiscoveryPayload());
}
@Test
public void testWebsocketSyncPropertiesSetValue() {
- load(WebsocketSyncPropertiesConfiguration.class,
"shenyu.sync.websocket.enabled=false");
+ load(WebsocketSyncPropertiesConfiguration.class,
"shenyu.sync.websocket.enabled=false",
+ "shenyu.sync.websocket.compact-discovery-payload=true");
WebsocketSyncProperties websocketSyncProperties =
getContext().getBean(WebsocketSyncProperties.class);
websocketSyncProperties.setMessageMaxSize(0);
websocketSyncProperties.setAllowOrigins("allowOrigins");
@@ -48,6 +50,7 @@ public final class WebsocketSyncPropertiesTest extends
AbstractConfigurationTest
Assertions.assertEquals(websocketSyncProperties.getMessageMaxSize(),
0);
Assertions.assertEquals(websocketSyncProperties.getAllowOrigins(),
"allowOrigins");
Assertions.assertEquals(websocketSyncProperties.getToken(), "token");
+
Assertions.assertTrue(websocketSyncProperties.isCompactDiscoveryPayload());
}
@Configuration
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
index 4b0ccfdf60..165a0ff545 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/websocket/WebsocketDataChangedListenerTest.java
@@ -18,13 +18,18 @@
package org.apache.shenyu.admin.listener.websocket;
import com.fasterxml.jackson.databind.JsonNode;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.shenyu.admin.config.WebSocketSyncConfiguration;
+import org.apache.shenyu.admin.config.properties.WebsocketSyncProperties;
import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.dto.AppAuthData;
import org.apache.shenyu.common.dto.AuthParamData;
import org.apache.shenyu.common.dto.AuthPathData;
import org.apache.shenyu.common.dto.ConditionData;
import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
import org.apache.shenyu.common.dto.MetaData;
import org.apache.shenyu.common.dto.PluginData;
import org.apache.shenyu.common.dto.ProxyApiKeyData;
@@ -32,17 +37,24 @@ import org.apache.shenyu.common.dto.ProxySelectorData;
import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -344,6 +356,137 @@ public final class WebsocketDataChangedListenerTest {
}
}
+ @ParameterizedTest
+ @EnumSource(value = DataEventTypeEnum.class, names = {"UPDATE", "CREATE",
"DELETE", "MYSELF", "REFRESH"})
+ public void testCompactDiscoveryPreservesRuntimeData(final
DataEventTypeEnum eventType) {
+ DiscoverySyncData data = discoveryData();
+ final String original = GsonUtils.getInstance().toJson(data);
+ final String legacy = discoveryMessage(new
WebsocketDataChangedListener(), data, eventType);
+ WebsocketSyncProperties properties = new WebsocketSyncProperties();
+ properties.setCompactDiscoveryPayload(true);
+ WebsocketDataChangedListener configured =
(WebsocketDataChangedListener) new
WebSocketSyncConfiguration().websocketDataChangedListener(properties);
+ String compact = discoveryMessage(configured, data, eventType);
+ JsonObject compactFrame =
JsonParser.parseString(compact).getAsJsonObject();
+ JsonObject compactData =
compactFrame.getAsJsonArray("data").get(0).getAsJsonObject();
+ JsonObject compactUpstream =
compactData.getAsJsonArray("upstreamDataList").get(0).getAsJsonObject();
+ assertEquals(eventType.name(),
compactFrame.get("eventType").getAsString());
+ assertEquals("DISCOVER_UPSTREAM",
compactFrame.get("groupType").getAsString());
+ assertFalse(compactData.has("discoveryHandlerId"));
+ for (String field : List.of("id", "dateUpdated", "discoveryHandlerId",
"namespaceId")) {
+ assertFalse(compactUpstream.has(field), field);
+ }
+ DiscoverySyncData decoded =
GsonUtils.getInstance().fromJson(compactData.toString(),
DiscoverySyncData.class);
+ assertEquals(data.getSelectorId(), decoded.getSelectorId());
+ assertEquals(data.getSelectorName(), decoded.getSelectorName());
+ assertEquals(data.getPluginName(), decoded.getPluginName());
+ assertEquals(data.getNamespaceId(), decoded.getNamespaceId());
+ DiscoveryUpstreamData expected = data.getUpstreamDataList().get(0);
+ DiscoveryUpstreamData actual = decoded.getUpstreamDataList().get(0);
+ assertEquals(expected.getDateCreated(), actual.getDateCreated());
+ assertEquals(expected.getProtocol(), actual.getProtocol());
+ assertEquals(expected.getUrl(), actual.getUrl());
+ assertEquals(expected.getStatus(), actual.getStatus());
+ assertEquals(expected.getWeight(), actual.getWeight());
+ assertEquals(expected.getProps(), actual.getProps());
+ assertEquals(original, GsonUtils.getInstance().toJson(data));
+ assertTrue(legacy.contains("dateUpdated"));
+ assertTrue(compact.length() < legacy.length());
+ }
+
+ @Test
+ public void testCompactDiscoveryRetainsEmptyUpstreamList() {
+ DiscoverySyncData data = discoveryData();
+ data.setUpstreamDataList(Collections.emptyList());
+ String message = discoveryMessage(new
WebsocketDataChangedListener(true), data, DataEventTypeEnum.UPDATE);
+ JsonObject decoded =
JsonParser.parseString(message).getAsJsonObject().getAsJsonArray("data").get(0).getAsJsonObject();
+ assertTrue(decoded.getAsJsonArray("upstreamDataList").isEmpty());
+ }
+
+ @Test
+ public void testCompactDiscoverySeparatesNamespacesAndSkipsNullRows() {
+ DiscoverySyncData first = discoveryData();
+ DiscoverySyncData second = discoveryData();
+ second.setNamespaceId("other-namespace");
+ second.setSelectorId("other-selector");
+ List<DiscoverySyncData> data = new ArrayList<>();
+ data.add(first);
+ data.add(null);
+ data.add(second);
+ try (MockedStatic<WebsocketCollector> collector =
mockStatic(WebsocketCollector.class)) {
+ new
WebsocketDataChangedListener(true).onDiscoveryUpstreamChanged(data,
DataEventTypeEnum.UPDATE);
+ for (DiscoverySyncData item : List.of(first, second)) {
+ collector.verify(() ->
WebsocketCollector.send(eq(item.getNamespaceId()), argThat(message -> {
+ JsonObject frame =
JsonParser.parseString(message).getAsJsonObject();
+ return frame.getAsJsonArray("data").size() == 1
+ &&
item.getSelectorId().equals(frame.getAsJsonArray("data").get(0).getAsJsonObject().get("selectorId").getAsString());
+ }), eq(DataEventTypeEnum.UPDATE)));
+ }
+ }
+ }
+
+ @Test
+ public void testCompactDiscoveryPreservesMissingListAndDefaultNamespace() {
+ DiscoverySyncData data = discoveryData();
+ data.setNamespaceId(null);
+ data.setUpstreamDataList(null);
+ try (MockedStatic<WebsocketCollector> collector =
mockStatic(WebsocketCollector.class)) {
+ new
WebsocketDataChangedListener(true).onDiscoveryUpstreamChanged(Collections.singletonList(data),
DataEventTypeEnum.DELETE);
+ collector.verify(() ->
WebsocketCollector.send(eq(Constants.SYS_DEFAULT_NAMESPACE_ID), argThat(message
-> {
+ JsonObject item =
JsonParser.parseString(message).getAsJsonObject().getAsJsonArray("data").get(0).getAsJsonObject();
+ return !item.has("upstreamDataList") &&
"selector-id".equals(item.get("selectorId").getAsString());
+ }), eq(DataEventTypeEnum.DELETE)));
+ }
+ }
+
+ @Test
+ public void testCompactDiscoveryPreservesNullUpstreamEntries() {
+ DiscoverySyncData data = discoveryData();
+ List<DiscoveryUpstreamData> upstreams = new
ArrayList<>(data.getUpstreamDataList());
+ upstreams.add(null);
+ data.setUpstreamDataList(upstreams);
+ String message = discoveryMessage(new
WebsocketDataChangedListener(true), data, DataEventTypeEnum.UPDATE);
+ JsonObject item =
JsonParser.parseString(message).getAsJsonObject().getAsJsonArray("data").get(0).getAsJsonObject();
+ assertEquals(2, item.getAsJsonArray("upstreamDataList").size());
+
assertTrue(item.getAsJsonArray("upstreamDataList").get(1).isJsonNull());
+ }
+
+ private String discoveryMessage(final WebsocketDataChangedListener
listener, final DiscoverySyncData data,
+ final DataEventTypeEnum eventType) {
+ List<String> messages = new ArrayList<>();
+ try (MockedStatic<WebsocketCollector> collector =
mockStatic(WebsocketCollector.class)) {
+ collector.when(() ->
WebsocketCollector.send(eq(data.getNamespaceId()), anyString(), eq(eventType)))
+ .thenAnswer(invocation -> {
+ messages.add(invocation.getArgument(1));
+ return null;
+ });
+
listener.onDiscoveryUpstreamChanged(Collections.singletonList(data), eventType);
+ }
+ assertEquals(1, messages.size());
+ return messages.get(0);
+ }
+
+ private DiscoverySyncData discoveryData() {
+ DiscoveryUpstreamData upstream = new DiscoveryUpstreamData();
+ upstream.setId("row-id");
+ upstream.setDateCreated(new Timestamp(1700000000000L));
+ upstream.setDateUpdated(new Timestamp(1700000001000L));
+ upstream.setDiscoveryHandlerId("handler-id");
+ upstream.setProtocol("http://");
+ upstream.setUrl("127.0.0.1:8080");
+ upstream.setStatus(0);
+ upstream.setWeight(100);
+ upstream.setProps("{\"warmup\":600000,\"healthCheckEnabled\":false}");
+ upstream.setNamespaceId(Constants.SYS_DEFAULT_NAMESPACE_ID);
+ DiscoverySyncData data = new DiscoverySyncData();
+ data.setSelectorId("selector-id");
+ data.setSelectorName("tcp-route");
+ data.setPluginName("divide");
+ data.setNamespaceId(Constants.SYS_DEFAULT_NAMESPACE_ID);
+ data.setDiscoveryHandlerId("handler-id");
+ data.setUpstreamDataList(Collections.singletonList(upstream));
+ return data;
+ }
+
/**
* test DiscoverySyncData with empty list — no send.
*/