From 9380433f7d39881ee1c6c83b175a5b218b90ec16 Mon Sep 17 00:00:00 2001 From: vzagorenko Date: Wed, 8 Jul 2026 18:31:30 +0200 Subject: [PATCH] fix(nsx): preserve externally-created active LB monitors on pool patch createNsxLbServerPool() replaced LBPool.ActiveMonitorPaths entirely with a single, app-managed monitor path whenever the pool was patched (e.g. on a member list change). Any active monitor attached to the pool outside of this codebase (manually, or by another process) was silently detached on the next update. The pool state already fetched for the member-diff check is now reused to also merge active monitor paths: monitors whose id does not contain the pool name are preserved, and only the app-owned monitor path is added/refreshed. Verified on staging NSX: a manually attached foreign monitor survives a pool member list update. --- .../cloudstack/service/NsxApiClient.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java index 61f4d74f7035..285db89b8824 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java @@ -696,6 +696,41 @@ List getLbPoolMembers(List memberList, Stri } return members; } + + /** + * Builds the final list of active monitor paths to set on the pool: + * any monitor already attached to the pool that was NOT created by + * this codebase (its id does not contain the pool name - consistent + * with the check already used in deleteNsxLbResources) is preserved + * as-is, plus the monitor path managed by this codebase. Without + * this, every pool update (e.g. on member list change) would + * silently detach any monitor attached outside of this plugin. + */ + private List mergeWithForeignActiveMonitorPaths(Optional existingPool, String lbServerPoolName, + String activeMonitorPath) { + List result = new ArrayList<>(); + List existingMonitorPaths = existingPool + .map(LBPool::getActiveMonitorPaths) + .orElseGet(List::of); + for (String path : existingMonitorPaths) { + if (!isMonitorOwnedByThisPool(path, lbServerPoolName)) { + result.add(path); + } + } + if (activeMonitorPath != null && !result.contains(activeMonitorPath)) { + result.add(activeMonitorPath); + } + return result; + } + + private boolean isMonitorOwnedByThisPool(String monitorPath, String lbServerPoolName) { + if (monitorPath == null) { + return false; + } + String monitorId = monitorPath.substring(monitorPath.lastIndexOf('/') + 1); + return monitorId.contains(lbServerPoolName); + } + public void createNsxLbServerPool(List memberList, String tier1GatewayName, String lbServerPoolName, String algorithm, String privatePort, String protocol) { try { @@ -712,13 +747,15 @@ public void createNsxLbServerPool(List memberList, String } } String activeMonitorPath = getLbActiveMonitorPath(lbServerPoolName, privatePort, protocol); + List activeMonitorPaths = mergeWithForeignActiveMonitorPaths( + nsxLbServerPool, lbServerPoolName, activeMonitorPath); LBPool lbPool = new LBPool.Builder() .setId(lbServerPoolName) .setDisplayName(lbServerPoolName) .setAlgorithm(getLoadBalancerAlgorithm(algorithm)) .setMembers(members) .setPassiveMonitorPath(NSX_LB_PASSIVE_MONITOR) - .setActiveMonitorPaths(List.of(activeMonitorPath)) + .setActiveMonitorPaths(activeMonitorPaths) .build(); lbPools.patch(lbServerPoolName, lbPool); } catch (Error error) {