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 0e388ff338 fix(admin): notify gateways when discovery upstreams are
deleted (#7819)
0e388ff338 is described below
commit 0e388ff33841246b44f223e59f9d9e7f16a80fb2
Author: Sean-Walker0 <[email protected]>
AuthorDate: Mon Oct 19 14:00:58 2026 +0800
fix(admin): notify gateways when discovery upstreams are deleted (#7819)
DELETE /discovery-upstream/batch removed the database rows and
returned success without publishing the remaining upstream snapshot,
so connected gateways kept the deleted URL as a routing candidate
until some other change triggered a refresh. The sibling create and
update paths already publish through fetchAll.
Resolve the owning discovery handlers before deleting the rows, then
publish the remaining upstream list for each affected handler —
including an empty list when the last upstream was deleted.
Regression test deletes the last upstream of a handler and asserts
changeUpstream is invoked with the empty remaining snapshot (red on
the previous code, which never notified the processor).
Co-authored-by: Sean-Walker0
<[email protected]>
---
.../service/impl/DiscoveryUpstreamServiceImpl.java | 5 ++-
.../service/DiscoveryUpstreamServiceTest.java | 37 ++++++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
index 82ad6f7f8d..515a6b7e63 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryUpstreamServiceImpl.java
@@ -179,8 +179,11 @@ public class DiscoveryUpstreamServiceImpl implements
DiscoveryUpstreamService {
*/
@Override
public String delete(final List<String> ids) {
-
+ // resolve the owning handlers before the rows disappear, so the
gateways can be
+ // notified with the remaining upstream snapshot (including an empty
one)
+ final List<DiscoveryUpstreamDO> deleted =
discoveryUpstreamMapper.selectByIds(ids);
discoveryUpstreamMapper.deleteByIds(ids);
+
deleted.stream().map(DiscoveryUpstreamDO::getDiscoveryHandlerId).distinct().forEach(this::fetchAll);
return ShenyuResultMessage.DELETE_SUCCESS;
}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java
index 7653058478..7f4f0e66cd 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryUpstreamServiceTest.java
@@ -52,6 +52,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.Timestamp;
@@ -64,6 +65,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.when;
import org.springframework.test.util.ReflectionTestUtils;
@@ -147,6 +149,41 @@ public final class DiscoveryUpstreamServiceTest {
assertEquals(ShenyuResultMessage.DELETE_SUCCESS, delete);
}
+
+ @Test
+ public void testDeleteNotifiesAffectedHandlersWithRemainingUpstreams() {
+ final DiscoveryUpstreamDO upstream = new DiscoveryUpstreamDO();
+ upstream.setId("upstream-1");
+ upstream.setDiscoveryHandlerId("handler-1");
+
when(discoveryUpstreamMapper.selectByIds(Collections.singletonList("upstream-1"))).thenReturn(Collections.singletonList(upstream));
+ when(discoveryUpstreamMapper.deleteByIds(any())).thenReturn(1);
+ // fetchAll dependencies for handler-1
+
when(discoveryUpstreamMapper.selectByDiscoveryHandlerId("handler-1")).thenReturn(Collections.emptyList());
+ final DiscoveryHandlerDO handler = new DiscoveryHandlerDO();
+ handler.setId("handler-1");
+
when(discoveryHandlerMapper.selectById("handler-1")).thenReturn(handler);
+
when(proxySelectorMapper.selectByHandlerId("handler-1")).thenReturn(null);
+ final SelectorDO selector = new SelectorDO();
+ selector.setId("selector-1");
+ selector.setPluginId("plugin-1");
+
when(selectorMapper.selectByDiscoveryHandlerId("handler-1")).thenReturn(selector);
+ final PluginDO plugin = new PluginDO();
+ plugin.setName("divide");
+ when(pluginMapper.selectById("plugin-1")).thenReturn(plugin);
+ handler.setDiscoveryId("discovery-1");
+ final DiscoveryDO discovery = new DiscoveryDO();
+ discovery.setId("discovery-1");
+ discovery.setDiscoveryType("local");
+ when(discoveryMapper.selectById("discovery-1")).thenReturn(discovery);
+
when(discoveryProcessorHolder.chooseProcessor(anyString())).thenReturn(discoveryProcessor);
+
+ String delete =
discoveryUpstreamService.delete(Collections.singletonList("upstream-1"));
+
+ assertEquals(ShenyuResultMessage.DELETE_SUCCESS, delete);
+ // the empty remaining snapshot must be published for the affected
handler
+ verify(discoveryProcessor,
Mockito.timeout(1000).times(1)).changeUpstream(any(),
eq(Collections.emptyList()));
+ }
+
@ParameterizedTest
@ValueSource(strings = {"handler", "selector", "plugin", "discovery"})
void rejectsMissingDiscoveryBindingsWithDomainErrors(final String missing)
{