From 6496cc3af04627d91a99462c4d9e5ce34d6944b5 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 27 May 2026 21:57:09 +0800 Subject: [PATCH 1/3] [vm]: split attachable l3 filters Split attachable L3 domain filtering by SDN vendor. Keep non-domain filters as common AND constraints. Resolves: ZCF-4049 Change-Id: I417ca4abbe6dfdd5cabea67edcbcace4f2681acd --- .../org/zstack/compute/vm/VmInstanceBase.java | 81 +++++++++++++++++-- ...leL3NetworkDomainFilterExtensionPoint.java | 11 +++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index e4a581eac2f..4ffbba70388 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -45,6 +45,8 @@ import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.message.*; import org.zstack.header.network.l3.*; +import org.zstack.header.network.sdncontroller.SdnControllerVO; +import org.zstack.header.network.sdncontroller.SdnControllerVO_; import org.zstack.header.storage.primary.*; import org.zstack.header.tag.SystemTagInventory; import org.zstack.header.vm.*; @@ -65,6 +67,7 @@ import org.zstack.identity.Account; import org.zstack.identity.AccountManager; import org.zstack.network.l3.IpRangeHelper; +import org.zstack.network.l3.L3NetworkHelper; import org.zstack.network.l3.L3NetworkManager; import org.zstack.resourceconfig.ResourceConfig; import org.zstack.resourceconfig.ResourceConfigFacade; @@ -5102,10 +5105,7 @@ private void handle(APIGetVmAttachableL3NetworkMsg msg) { APIGetVmAttachableL3NetworkReply reply = new APIGetVmAttachableL3NetworkReply(); List l3Invs = getAttachableL3Network(msg.getSession().getAccountUuid()); - List ret = new ArrayList<>(l3Invs); - for (FilterAttachableL3NetworkExtensionPoint ext : pluginRgty.getExtensionList(FilterAttachableL3NetworkExtensionPoint.class)) { - ret = ext.filterAttachableL3Network(VmInstanceInventory.valueOf(self), ret); - } + List ret = filterAttachableL3Network(VmInstanceInventory.valueOf(self), l3Invs); reply.setInventories(ret); bus.reply(msg, reply); @@ -5115,10 +5115,7 @@ private void handle(APIGetCandidateL3NetworksForChangeVmNicNetworkMsg msg) { APIGetVmAttachableL3NetworkReply reply = new APIGetVmAttachableL3NetworkReply(); List l3Invs = getAttachableL3Network(msg.getSession().getAccountUuid()); - List ret = new ArrayList<>(l3Invs); - for (FilterAttachableL3NetworkExtensionPoint ext : pluginRgty.getExtensionList(FilterAttachableL3NetworkExtensionPoint.class)) { - ret = ext.filterAttachableL3Network(VmInstanceInventory.valueOf(self), ret); - } + List ret = filterAttachableL3Network(VmInstanceInventory.valueOf(self), l3Invs); VmNicVO nicVO= Q.New(VmNicVO.class).eq(VmNicVO_.uuid, msg.getVmNicUuid()).find(); for (FilterVmNicChangeableL3NetworkExtensionPoint ext : pluginRgty.getExtensionList(FilterVmNicChangeableL3NetworkExtensionPoint.class)) { @@ -5129,6 +5126,74 @@ private void handle(APIGetCandidateL3NetworksForChangeVmNicNetworkMsg msg) { bus.reply(msg, reply); } + private List filterAttachableL3Network(VmInstanceInventory vm, List l3Invs) { + List ret = filterAttachableL3NetworkByDomain(vm, l3Invs); + for (FilterAttachableL3NetworkExtensionPoint ext : pluginRgty.getExtensionList(FilterAttachableL3NetworkExtensionPoint.class)) { + if (ext instanceof VmAttachableL3NetworkDomainFilterExtensionPoint) { + continue; + } + ret = ext.filterAttachableL3Network(vm, ret); + } + return ret; + } + + private List filterAttachableL3NetworkByDomain(VmInstanceInventory vm, List l3Invs) { + Set domainFilters = new LinkedHashSet<>( + pluginRgty.getExtensionList(VmAttachableL3NetworkDomainFilterExtensionPoint.class)); + pluginRgty.getExtensionList(FilterAttachableL3NetworkExtensionPoint.class).stream() + .filter(VmAttachableL3NetworkDomainFilterExtensionPoint.class::isInstance) + .map(VmAttachableL3NetworkDomainFilterExtensionPoint.class::cast) + .forEach(domainFilters::add); + if (domainFilters.isEmpty()) { + return new ArrayList<>(l3Invs); + } + + Map> filtersByVendor = new HashMap<>(); + List defaultFilters = new ArrayList<>(); + for (VmAttachableL3NetworkDomainFilterExtensionPoint filter : domainFilters) { + String vendorType = filter.getSdnControllerVendorType(); + if (vendorType == null) { + defaultFilters.add(filter); + } else { + filtersByVendor.computeIfAbsent(vendorType, k -> new ArrayList<>()).add(filter); + } + } + Map> l3sByVendor = new LinkedHashMap<>(); + for (L3NetworkInventory l3 : l3Invs) { + String vendorType = getSdnControllerVendorType(l3.getUuid()); + l3sByVendor.computeIfAbsent(vendorType, k -> new ArrayList<>()).add(l3); + } + + List ret = new ArrayList<>(); + for (Map.Entry> e : l3sByVendor.entrySet()) { + List filters = e.getKey() == null ? + defaultFilters : filtersByVendor.getOrDefault(e.getKey(), Collections.emptyList()); + if (filters.isEmpty()) { + ret.addAll(e.getValue()); + continue; + } + + List filtered = new ArrayList<>(e.getValue()); + for (VmAttachableL3NetworkDomainFilterExtensionPoint filter : filters) { + filtered = filter.filterAttachableL3NetworkInDomain(vm, filtered); + } + ret.addAll(filtered); + } + return ret; + } + + private String getSdnControllerVendorType(String l3NetworkUuid) { + String controllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(l3NetworkUuid); + if (controllerUuid == null) { + return null; + } + + return Q.New(SdnControllerVO.class) + .select(SdnControllerVO_.vendorType) + .eq(SdnControllerVO_.uuid, controllerUuid) + .findValue(); + } + private void handle(final AttachIsoToVmInstanceMsg msg) { thdf.chainSubmit(new ChainTask(msg) { @Override diff --git a/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java new file mode 100644 index 00000000000..a79eba41786 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.vm; + +import org.zstack.header.network.l3.L3NetworkInventory; + +import java.util.List; + +public interface VmAttachableL3NetworkDomainFilterExtensionPoint { + String getSdnControllerVendorType(); + + List filterAttachableL3NetworkInDomain(VmInstanceInventory vm, List l3s); +} From cc254e16d164a193b8f06246aefd7680a77c8827 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Thu, 28 May 2026 09:33:19 +0800 Subject: [PATCH 2/3] [vm]: address attachable l3 review Batch SDN vendor lookup for attachable L3 filters. Document the domain filter extension contract. Resolves: ZCF-4049 Change-Id: Iaaa3e66a1be47840999965330cc4efbb4c96f3b1 --- .../org/zstack/compute/vm/VmInstanceBase.java | 87 ++++++++++++++++--- ...leL3NetworkDomainFilterExtensionPoint.java | 12 +++ 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index 4ffbba70388..8653f86d0e4 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -44,11 +44,14 @@ import org.zstack.header.image.*; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.message.*; +import org.zstack.header.network.l2.L2NetworkVO; import org.zstack.header.network.l3.*; import org.zstack.header.network.sdncontroller.SdnControllerVO; import org.zstack.header.network.sdncontroller.SdnControllerVO_; import org.zstack.header.storage.primary.*; import org.zstack.header.tag.SystemTagInventory; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.tag.SystemTagVO_; import org.zstack.header.vm.*; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicHostUuid; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicVmState; @@ -66,8 +69,8 @@ import org.zstack.header.volume.*; import org.zstack.identity.Account; import org.zstack.identity.AccountManager; +import org.zstack.network.l2.L2NetworkSystemTags; import org.zstack.network.l3.IpRangeHelper; -import org.zstack.network.l3.L3NetworkHelper; import org.zstack.network.l3.L3NetworkManager; import org.zstack.resourceconfig.ResourceConfig; import org.zstack.resourceconfig.ResourceConfigFacade; @@ -88,6 +91,7 @@ import org.zstack.utils.network.NetworkUtils; import javax.persistence.PersistenceException; +import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -5158,9 +5162,10 @@ private List filterAttachableL3NetworkByDomain(VmInstanceInv filtersByVendor.computeIfAbsent(vendorType, k -> new ArrayList<>()).add(filter); } } + Map vendorTypeByL3Uuid = getSdnControllerVendorTypeByL3Uuid(l3Invs); Map> l3sByVendor = new LinkedHashMap<>(); for (L3NetworkInventory l3 : l3Invs) { - String vendorType = getSdnControllerVendorType(l3.getUuid()); + String vendorType = vendorTypeByL3Uuid.get(l3.getUuid()); l3sByVendor.computeIfAbsent(vendorType, k -> new ArrayList<>()).add(l3); } @@ -5182,16 +5187,78 @@ private List filterAttachableL3NetworkByDomain(VmInstanceInv return ret; } - private String getSdnControllerVendorType(String l3NetworkUuid) { - String controllerUuid = L3NetworkHelper.getSdnControllerUuidFromL3Uuid(l3NetworkUuid); - if (controllerUuid == null) { - return null; + private Map getSdnControllerVendorTypeByL3Uuid(List l3Invs) { + if (l3Invs.isEmpty()) { + return Collections.emptyMap(); } - return Q.New(SdnControllerVO.class) - .select(SdnControllerVO_.vendorType) - .eq(SdnControllerVO_.uuid, controllerUuid) - .findValue(); + List l3Uuids = l3Invs.stream() + .map(L3NetworkInventory::getUuid) + .collect(Collectors.toList()); + List l3L2Tuples = Q.New(L3NetworkVO.class) + .select(L3NetworkVO_.uuid, L3NetworkVO_.l2NetworkUuid) + .in(L3NetworkVO_.uuid, l3Uuids) + .listTuple(); + + Map l3ToL2Uuid = new HashMap<>(); + Set l2Uuids = new HashSet<>(); + for (Tuple t : l3L2Tuples) { + String l3Uuid = t.get(0, String.class); + String l2Uuid = t.get(1, String.class); + l3ToL2Uuid.put(l3Uuid, l2Uuid); + if (l2Uuid != null) { + l2Uuids.add(l2Uuid); + } + } + + if (l2Uuids.isEmpty()) { + return Collections.emptyMap(); + } + + List controllerTagTuples = Q.New(SystemTagVO.class) + .select(SystemTagVO_.resourceUuid, SystemTagVO_.tag) + .in(SystemTagVO_.resourceUuid, l2Uuids) + .eq(SystemTagVO_.resourceType, L2NetworkVO.class.getSimpleName()) + .like(SystemTagVO_.tag, L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN + "::%") + .listTuple(); + Map l2ToControllerUuid = new HashMap<>(); + Set controllerUuids = new HashSet<>(); + for (Tuple t : controllerTagTuples) { + String l2Uuid = t.get(0, String.class); + String tag = t.get(1, String.class); + String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByTag( + tag, L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); + if (controllerUuid != null) { + l2ToControllerUuid.put(l2Uuid, controllerUuid); + controllerUuids.add(controllerUuid); + } + } + + if (controllerUuids.isEmpty()) { + return Collections.emptyMap(); + } + + List controllerVOS = Q.New(SdnControllerVO.class) + .in(SdnControllerVO_.uuid, controllerUuids) + .list(); + Map controllerUuidToVendorType = new HashMap<>(); + for (SdnControllerVO vo : controllerVOS) { + controllerUuidToVendorType.put(vo.getUuid(), vo.getVendorType()); + } + + Map vendorTypeByL3Uuid = new HashMap<>(); + for (Map.Entry e : l3ToL2Uuid.entrySet()) { + String controllerUuid = l2ToControllerUuid.get(e.getValue()); + if (controllerUuid == null) { + continue; + } + + String vendorType = controllerUuidToVendorType.get(controllerUuid); + if (vendorType != null) { + vendorTypeByL3Uuid.put(e.getKey(), vendorType); + } + } + return vendorTypeByL3Uuid; } private void handle(final AttachIsoToVmInstanceMsg msg) { diff --git a/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java index a79eba41786..f4e0a0b744c 100644 --- a/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/vm/VmAttachableL3NetworkDomainFilterExtensionPoint.java @@ -5,7 +5,19 @@ import java.util.List; public interface VmAttachableL3NetworkDomainFilterExtensionPoint { + /** + * Returns the SDN controller vendor this domain filter handles. + * + * @return vendor type for SDN-backed L3 networks, or null for non-SDN/default L3 networks + */ String getSdnControllerVendorType(); + /** + * Keeps the L3 networks that are attachable to the VM within this filter's network domain. + * + * @param vm non-null VM inventory used as the attach target + * @param l3s non-null candidate L3 list; implementations must not modify the input list + * @return non-null filtered candidate list containing attachable L3 networks in this domain + */ List filterAttachableL3NetworkInDomain(VmInstanceInventory vm, List l3s); } From 46af5696c6d8c150572a347adf921cc0f6bdea23 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Thu, 28 May 2026 09:57:30 +0800 Subject: [PATCH 3/3] [vm]: keep unresolved sdn separate Do not route unresolved SDN vendors to default filters. Resolves: ZCF-4049 Change-Id: I257433e821925538f2a1ad0b68788a20a020bd17 --- .../org/zstack/compute/vm/VmInstanceBase.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index 8653f86d0e4..9ceb8000912 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -107,6 +107,7 @@ public class VmInstanceBase extends AbstractVmInstance { protected static final CLogger logger = Utils.getLogger(VmInstanceBase.class); private static final String ATTACH_CREATED_VM_SYSTEM_TAG_UUIDS = "AttachCreatedVmSystemTagUuids"; + private static final String UNRESOLVED_SDN_VENDOR = "__UNRESOLVED_SDN_VENDOR__"; @Autowired protected CloudBus bus; @@ -5171,8 +5172,14 @@ private List filterAttachableL3NetworkByDomain(VmInstanceInv List ret = new ArrayList<>(); for (Map.Entry> e : l3sByVendor.entrySet()) { - List filters = e.getKey() == null ? - defaultFilters : filtersByVendor.getOrDefault(e.getKey(), Collections.emptyList()); + List filters; + if (e.getKey() == null) { + filters = defaultFilters; + } else if (UNRESOLVED_SDN_VENDOR.equals(e.getKey())) { + filters = Collections.emptyList(); + } else { + filters = filtersByVendor.getOrDefault(e.getKey(), Collections.emptyList()); + } if (filters.isEmpty()) { ret.addAll(e.getValue()); continue; @@ -5254,9 +5261,8 @@ private Map getSdnControllerVendorTypeByL3Uuid(List