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

wy471x 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 b56b5e009d fix(admin): skip discovery rels whose import remapping 
dropped the reference (#7515)
b56b5e009d is described below

commit b56b5e009db2569fc48a34980bfc0c993b4c5bbe
Author: Sean-Walker0 <[email protected]>
AuthorDate: Sat Oct 10 21:55:37 2026 +0800

    fix(admin): skip discovery rels whose import remapping dropped the 
reference (#7515)
    
    * fix(admin): skip discovery rels whose import remapping dropped the 
reference
    
    configsImport remapped discovery_rel selector and proxy-selector ids
    through the import context mappings and inserted the row even when the
    mapping was missing, persisting relations whose reference columns were
    silently nulled (insertSelective omits null columns). The imported
    namespace then contained discovery_rel rows pointing at nothing.
    
    Resolve both references first and skip the rel when the exported id was
    set but its target was not imported into this namespace (or when the
    discovery carries no handler to attach the rel to), logging the skip.
    Relations whose exported references were already null keep that shape.
    Regression tests cover the selector-mapping miss, the proxy-selector
    mapping miss (both red on the previous code, which inserted the row),
    and the successful remap path.
    
    Fixes #7469
    
    * fix(admin): keep blank discovery rel references intact on import
    
    Review follow-up: rels created for a TCP proxy selector store an empty
    selector_id ('' in the database, not NULL), and the previous
    nonNull-based check treated that blank as a remappable reference,
    mapping.get("") returned null, and the rel was skipped even though its
    proxy-selector reference remapped fine. The import of a namespace
    containing a TCP proxy selector plus its discovery silently lost the
    binding.
    
    Only a non-blank reference is a remappable target now; blank
    references keep their exported shape. Regression test replays the
    rel shape produced by ProxySelectorServiceImpl#create (selectorId
    empty, proxySelectorId remapped) and asserts the row is inserted with
    both fields correct.
    
    ---------
    
    Co-authored-by: Sean-Walker0 
<[email protected]>
---
 .../admin/service/impl/DiscoveryServiceImpl.java   | 25 ++++--
 .../admin/service/DiscoveryServiceImplTest.java    | 91 ++++++++++++++++++++++
 2 files changed, 110 insertions(+), 6 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
index cbbdf9dbef..3d1a2e9b62 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/DiscoveryServiceImpl.java
@@ -532,12 +532,25 @@ public class DiscoveryServiceImpl implements 
DiscoveryService {
                         .INSTANCE
                         .mapToDO(discoveryDTO.getDiscoveryRel());
                 discoveryRelDO.setDiscoveryHandlerId(discoveryHandlerId);
-                Optional.ofNullable(discoveryRelDO.getSelectorId())
-                                .ifPresent(selectorId -> 
discoveryRelDO.setSelectorId(context.getSelectorIdMapping().get(selectorId)));
-                Optional.ofNullable(discoveryRelDO.getProxySelectorId())
-                        .ifPresent(proxySelectorId -> 
discoveryRelDO.setProxySelectorId(context.getProxySelectorIdMapping().get(proxySelectorId)));
-                
discoveryRelDO.setId(UUIDUtils.getInstance().generateShortUuid());
-                discoveryRelMapper.insertSelective(discoveryRelDO);
+                // rels created for a TCP proxy selector store an empty 
selector_id ('' in the
+                // database, not NULL), so only a non-blank reference is a 
remappable target;
+                // blank references keep their exported shape
+                String remappedSelectorId = 
StringUtils.isBlank(discoveryRelDO.getSelectorId())
+                        ? discoveryRelDO.getSelectorId() : 
context.getSelectorIdMapping().get(discoveryRelDO.getSelectorId());
+                String remappedProxySelectorId = 
StringUtils.isBlank(discoveryRelDO.getProxySelectorId())
+                        ? discoveryRelDO.getProxySelectorId() : 
context.getProxySelectorIdMapping().get(discoveryRelDO.getProxySelectorId());
+                boolean selectorReferenceLost = 
StringUtils.isNotBlank(discoveryRelDO.getSelectorId()) && 
Objects.isNull(remappedSelectorId);
+                boolean proxySelectorReferenceLost = 
StringUtils.isNotBlank(discoveryRelDO.getProxySelectorId()) && 
Objects.isNull(remappedProxySelectorId);
+                if (Objects.isNull(discoveryHandlerId) || 
selectorReferenceLost || proxySelectorReferenceLost) {
+                    // the relation's target was not imported into this 
namespace; skip it instead
+                    // of persisting a row whose selector/proxy-selector 
reference was nulled by remapping
+                    LOG.warn("skip discovery rel of discovery [{}]: referenced 
selector/proxy selector was not imported", discoveryName);
+                } else {
+                    discoveryRelDO.setSelectorId(remappedSelectorId);
+                    discoveryRelDO.setProxySelectorId(remappedProxySelectorId);
+                    
discoveryRelDO.setId(UUIDUtils.getInstance().generateShortUuid());
+                    discoveryRelMapper.insertSelective(discoveryRelDO);
+                }
             }
         }
         
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
index c84d994b1e..b640bcf47a 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
@@ -24,7 +24,12 @@ import org.apache.shenyu.admin.mapper.DiscoveryMapper;
 import org.apache.shenyu.admin.mapper.DiscoveryRelMapper;
 import org.apache.shenyu.admin.mapper.ProxySelectorMapper;
 import org.apache.shenyu.admin.mapper.SelectorMapper;
+import org.apache.shenyu.admin.model.dto.DiscoveryDTO;
+import org.apache.shenyu.admin.model.dto.DiscoveryHandlerDTO;
+import org.apache.shenyu.admin.model.dto.DiscoveryRelDTO;
 import org.apache.shenyu.admin.model.entity.DiscoveryDO;
+import org.apache.shenyu.admin.model.entity.DiscoveryRelDO;
+import org.apache.shenyu.admin.service.configs.ConfigsImportContext;
 import org.apache.shenyu.admin.model.entity.SelectorDO;
 import org.apache.shenyu.register.common.dto.DiscoveryConfigRegisterDTO;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -50,6 +55,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.BDDMockito.given;
+import org.mockito.ArgumentCaptor;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
@@ -187,4 +193,89 @@ public final class DiscoveryServiceImplTest {
         verify(discoveryMapper, never()).delete(any(), any());
         verify(discoveryProcessor, never()).removeDiscovery(any());
     }
+
+    @Test
+    public void testImportSkipsRelWhenSelectorMappingMissing() {
+        final ConfigsImportContext context = new ConfigsImportContext();
+        DiscoveryDTO discoveryDTO = buildImportDiscovery();
+        DiscoveryRelDTO rel = new DiscoveryRelDTO();
+        rel.setSelectorId("selector-exported");
+        discoveryDTO.setDiscoveryRel(rel);
+
+        
given(discoveryMapper.selectAllByNamespaceId("ns-import")).willReturn(Collections.emptyList());
+        given(discoveryMapper.insert(any(DiscoveryDO.class))).willReturn(1);
+
+        discoveryService.importData("ns-import", 
Collections.singletonList(discoveryDTO), context);
+
+        verify(discoveryRelMapper, 
never()).insertSelective(any(DiscoveryRelDO.class));
+    }
+
+    @Test
+    public void testImportSkipsRelWhenProxySelectorMappingMissing() {
+        final ConfigsImportContext context = new ConfigsImportContext();
+        DiscoveryDTO discoveryDTO = buildImportDiscovery();
+        DiscoveryRelDTO rel = new DiscoveryRelDTO();
+        rel.setProxySelectorId("proxy-selector-exported");
+        discoveryDTO.setDiscoveryRel(rel);
+
+        
given(discoveryMapper.selectAllByNamespaceId("ns-import")).willReturn(Collections.emptyList());
+        given(discoveryMapper.insert(any(DiscoveryDO.class))).willReturn(1);
+
+        discoveryService.importData("ns-import", 
Collections.singletonList(discoveryDTO), context);
+
+        verify(discoveryRelMapper, 
never()).insertSelective(any(DiscoveryRelDO.class));
+    }
+
+    @Test
+    public void testImportRemapsRelReferencesWhenMappingsExist() {
+        final ConfigsImportContext context = new ConfigsImportContext();
+        context.getSelectorIdMapping().put("selector-exported", 
"selector-new");
+        DiscoveryDTO discoveryDTO = buildImportDiscovery();
+        DiscoveryRelDTO rel = new DiscoveryRelDTO();
+        rel.setSelectorId("selector-exported");
+        discoveryDTO.setDiscoveryRel(rel);
+
+        
given(discoveryMapper.selectAllByNamespaceId("ns-import")).willReturn(Collections.emptyList());
+        given(discoveryMapper.insert(any(DiscoveryDO.class))).willReturn(1);
+
+        discoveryService.importData("ns-import", 
Collections.singletonList(discoveryDTO), context);
+
+        ArgumentCaptor<DiscoveryRelDO> relCaptor = 
ArgumentCaptor.forClass(DiscoveryRelDO.class);
+        verify(discoveryRelMapper).insertSelective(relCaptor.capture());
+        assertEquals("selector-new", relCaptor.getValue().getSelectorId());
+    }
+
+    private DiscoveryDTO buildImportDiscovery() {
+        DiscoveryDTO discoveryDTO = new DiscoveryDTO();
+        discoveryDTO.setName("discovery-import");
+        discoveryDTO.setPluginName("tcp");
+        discoveryDTO.setType("local");
+        DiscoveryHandlerDTO handlerDTO = new DiscoveryHandlerDTO();
+        handlerDTO.setId("handler-exported");
+        handlerDTO.setListenerNode("/shenyu/discovery");
+        handlerDTO.setHandler("");
+        discoveryDTO.setDiscoveryHandler(handlerDTO);
+        return discoveryDTO;
+    }
+
+    @Test
+    public void testImportKeepsBlankSelectorIdShapeAndRemapsProxySelector() {
+        ConfigsImportContext context = new ConfigsImportContext();
+        context.getProxySelectorIdMapping().put("proxy-selector-exported", 
"proxy-selector-new");
+        DiscoveryDTO discoveryDTO = buildImportDiscovery();
+        DiscoveryRelDTO rel = new DiscoveryRelDTO();
+        rel.setSelectorId("");
+        rel.setProxySelectorId("proxy-selector-exported");
+        discoveryDTO.setDiscoveryRel(rel);
+
+        
given(discoveryMapper.selectAllByNamespaceId("ns-import")).willReturn(Collections.emptyList());
+        given(discoveryMapper.insert(any(DiscoveryDO.class))).willReturn(1);
+
+        discoveryService.importData("ns-import", 
Collections.singletonList(discoveryDTO), context);
+
+        ArgumentCaptor<DiscoveryRelDO> relCaptor = 
ArgumentCaptor.forClass(DiscoveryRelDO.class);
+        verify(discoveryRelMapper).insertSelective(relCaptor.capture());
+        assertEquals("", relCaptor.getValue().getSelectorId());
+        assertEquals("proxy-selector-new", 
relCaptor.getValue().getProxySelectorId());
+    }
 }

Reply via email to