Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6b4521b
<fix>[vm]: use max of virtual and actual size for root disk when no d…
AlanJager Feb 12, 2026
3b5bda3
<fix>[zbs]: enable tryNext and 30s timeout for getActiveClients MDS call
AlanJager Feb 12, 2026
80df074
<fix>[vm]: add Destroying->Stopped state transition
AlanJager Feb 12, 2026
a84a36e
<fix>[ceph]: apply over-provisioning ratio when releasing snapshot ca…
AlanJager Feb 12, 2026
f19223a
<fix>[loadBalancer]: block SLB deletion during grayscale upgrade
AlanJager Feb 13, 2026
24d4f3b
<fix>[i18n]: improve snapshot error message for unattached volume
AlanJager Feb 13, 2026
f563992
<fix>[compute]: add null check for VmNicVO in afterDelIpAddress and a…
AlanJager Feb 13, 2026
6545350
<fix>[network]: filter reserved IPs from GetFreeIp API results
AlanJager Feb 13, 2026
76490a5
Merge branch 'fix/ZSTAC-80620' into '5.5.12'
Feb 16, 2026
461e8a2
Merge branch 'fix/ZSTAC-82153' into '5.5.12'
Feb 16, 2026
32e1e94
Merge branch 'fix/ZSTAC-80595' into '5.5.12'
Feb 16, 2026
addec8c
Merge branch 'fix/ZSTAC-74683' into '5.5.12'
Feb 16, 2026
26b8b1a
<fix>[mn]: synchronize hash ring operations to prevent dual-MN task s…
AlanJager Feb 12, 2026
be53c72
Merge branch 'fix/ZSTAC-81182' into '5.5.12'
Feb 16, 2026
e1dee9f
Merge branch 'fix/ZSTAC-79709' into '5.5.12'
Feb 16, 2026
3bd062b
Merge branch 'fix/ZSTAC-81741' into '5.5.12'
Feb 16, 2026
aaeaf39
<fix>[storage]: desensitize mdsUrls in ExternalPrimaryStorageInventory
AlanJager Feb 13, 2026
f41558d
<fix>[volumebackup]: add backup cancel timeout error code
AlanJager Feb 16, 2026
673be94
Merge branch 'fix/ZSTAC-77711' into '5.5.12'
Feb 16, 2026
7f53f5a
<fix>[thread]: guard Context.current() with telemetry check
PandaWuu Feb 16, 2026
72ce6ef
Merge branch 'bugfix/ZSTAC-82275' into '5.5.12'
PandaWuu Feb 16, 2026
34bceb1
Merge branch 'fix/ZSTAC-78989' into '5.5.12'
Feb 16, 2026
3e02188
Merge branch 'fix/ZSTAC-82195' into '5.5.12'
Feb 17, 2026
799a84f
Merge branch 'fix/ZSTAC-80664' into '5.5.12'
Feb 17, 2026
1e44aa7
<fix>[portForwarding]: serialize concurrent PF rule creation per VIP
AlanJager Feb 16, 2026
a5b5929
<fix>[portForwarding]: avoid deadlock by using distinct sync signatur…
AlanJager Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public void afterAddIpAddress(String vmNicUUid, String usedIpUuid) {
SQL.New(UsedIpVO.class).eq(UsedIpVO_.uuid, usedIpUuid).set(UsedIpVO_.vmNicUuid, vmNicUUid).update();

VmNicVO nic = Q.New(VmNicVO.class).eq(VmNicVO_.uuid, vmNicUUid).find();
if (nic == null) {
logger.debug(String.format("VmNic[uuid:%s] not found, skip afterAddIpAddress", vmNicUUid));
return;
}

UsedIpVO temp = null;
/* if there is ipv4 addresses, we put the first attached ipv4 address to VmNic.ip
Expand Down Expand Up @@ -88,6 +92,10 @@ public void afterAddIpAddress(String vmNicUUid, String usedIpUuid) {
@Override
public void afterDelIpAddress(String vmNicUUid, String usedIpUuid) {
VmNicVO nic = Q.New(VmNicVO.class).eq(VmNicVO_.uuid, vmNicUUid).find();
if (nic == null) {
logger.debug(String.format("VmNic[uuid:%s] not found, skip afterDelIpAddress", vmNicUUid));
return;
}
if (nic.getUsedIpUuid() != null && !nic.getUsedIpUuid().equals(usedIpUuid)) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion conf/i18n/globalErrorCodeMapping/global-error-en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -3374,7 +3374,7 @@
"ORG_ZSTACK_NETWORK_HUAWEI_IMASTER_10019": "delete token of SDN controller [IP:%s] failed because %s",
"ORG_ZSTACK_STORAGE_PRIMARY_BLOCK_10004": "Cannot execute volume mapping to host flow due to invalid volume ID.%s",
"ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10007": "port forwarding rule [uuid:%s] has not been attached to any virtual machine network interface, cannot detach",
"ORG_ZSTACK_MEVOCO_10088": "cannot take a snapshot for volumes[%s] when volume[uuid: %s] is not attached",
"ORG_ZSTACK_MEVOCO_10088": "cannot create snapshot for volume[uuid:%s] because it is not attached to any VM instance. Please attach the volume to a VM first. Affected volumes: %s",
"ORG_ZSTACK_STORAGE_PRIMARY_BLOCK_10005": "Cannot execute map LUN to host flow due to invalid LUN type: %s",
"ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10008": "port forwarding rule [uuid:%s] has been associated with vm nic [uuid:%s], cannot be reassigned again",
"ORG_ZSTACK_MEVOCO_10087": "A Running VM[uuid:%s] has no associated Host UUID.",
Expand Down
2 changes: 1 addition & 1 deletion conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3374,7 +3374,7 @@
"ORG_ZSTACK_NETWORK_HUAWEI_IMASTER_10019": "删除 SDN 控制器 [IP:%s] 的令牌失败,因为 %s",
"ORG_ZSTACK_STORAGE_PRIMARY_BLOCK_10004": "无法执行映射LUN到主机流程,无效的LUN ID",
"ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10007": "端口转发规则 rule[uuid:%s] 没有绑定到任何 VM 的网卡上,无法解除绑定",
"ORG_ZSTACK_MEVOCO_10088": "无法为挂载状态以外的卷[%s]创建快照",
"ORG_ZSTACK_MEVOCO_10088": "无法为云盘[uuid:%s]创建快照,因为该云盘未挂载到任何云主机。请先将云盘挂载到云主机后再创建快照。相关云盘: %s",
"ORG_ZSTACK_STORAGE_PRIMARY_BLOCK_10005": "无法执行映射LUN到主机流程,无效的LUN类型",
"ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10008": "端口转发规则[uuid:%s]已绑定到VM网卡[uuid:%s],无法再次绑定",
"ORG_ZSTACK_MEVOCO_10087": "如何一个运行中的VM[uuid:%s]没有宿主机uuid?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,27 @@ public class ResourceDestinationMakerImpl implements ManagementNodeChangeListene
private DatabaseFacade dbf;

@Override
public void nodeJoin(ManagementNodeInventory inv) {
public synchronized void nodeJoin(ManagementNodeInventory inv) {
nodeHash.add(inv.getUuid());
nodes.put(inv.getUuid(), new NodeInfo(inv));
}

@Override
public void nodeLeft(ManagementNodeInventory inv) {
public synchronized void nodeLeft(ManagementNodeInventory inv) {
String nodeId = inv.getUuid();
nodeHash.remove(nodeId);
nodes.remove(nodeId);
}

@Override
public void iAmDead(ManagementNodeInventory inv) {
public synchronized void iAmDead(ManagementNodeInventory inv) {
String nodeId = inv.getUuid();
nodeHash.remove(nodeId);
nodes.remove(nodeId);
}

@Override
public void iJoin(ManagementNodeInventory inv) {
public synchronized void iJoin(ManagementNodeInventory inv) {
List<ManagementNodeVO> lst = Q.New(ManagementNodeVO.class).list();
lst.forEach((ManagementNodeVO node) -> {
nodeHash.add(node.getUuid());
Expand All @@ -56,7 +56,7 @@ public void iJoin(ManagementNodeInventory inv) {
}

@Override
public String makeDestination(String resourceUuid) {
public synchronized String makeDestination(String resourceUuid) {
String nodeUuid = nodeHash.get(resourceUuid);
if (nodeUuid == null) {
throw new CloudRuntimeException("Cannot find any available management node to send message");
Expand All @@ -66,18 +66,18 @@ public String makeDestination(String resourceUuid) {
}

@Override
public boolean isManagedByUs(String resourceUuid) {
public synchronized boolean isManagedByUs(String resourceUuid) {
String nodeUuid = makeDestination(resourceUuid);
return nodeUuid.equals(Platform.getManagementServerId());
}

@Override
public Collection<String> getManagementNodesInHashRing() {
return nodeHash.getNodes();
public synchronized Collection<String> getManagementNodesInHashRing() {
return new ArrayList<>(nodeHash.getNodes());
}

@Override
public NodeInfo getNodeInfo(String nodeUuid) {
public synchronized NodeInfo getNodeInfo(String nodeUuid) {
NodeInfo info = nodes.get(nodeUuid);
if (info == null) {
ManagementNodeVO vo = dbf.findByUuid(nodeUuid, ManagementNodeVO.class);
Expand All @@ -93,17 +93,17 @@ public NodeInfo getNodeInfo(String nodeUuid) {
}

@Override
public Collection<NodeInfo> getAllNodeInfo() {
return nodes.values();
public synchronized Collection<NodeInfo> getAllNodeInfo() {
return new ArrayList<>(nodes.values());
}

@Override
public int getManagementNodeCount() {
return nodes.values().size();
public synchronized int getManagementNodeCount() {
return nodes.size();
}


public boolean isNodeInCircle(String nodeId) {
public synchronized boolean isNodeInCircle(String nodeId) {
return nodeHash.hasNode(nodeId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ private class SyncTaskFuture<T> extends AbstractFuture<T> {

public SyncTaskFuture(SyncTask<T> task) {
super(task);
this.parentContext = Context.current();
this.parentContext = isTelemetryEnabled() ? Context.current() : null;
}

private SyncTask getTask() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import org.zstack.header.storage.primary.PrimaryStorageInventory;
import org.zstack.utils.gson.JSONObjectUtil;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Inventory(mappingVOClass = ExternalPrimaryStorageVO.class)
Expand Down Expand Up @@ -59,6 +61,7 @@ public ExternalPrimaryStorageInventory(ExternalPrimaryStorageVO lvo) {
super(lvo);
identity = lvo.getIdentity();
config = JSONObjectUtil.toObject(lvo.getConfig(), LinkedHashMap.class);
desensitizeConfig(config);
addonInfo = JSONObjectUtil.toObject(lvo.getAddonInfo(), LinkedHashMap.class);
outputProtocols = lvo.getOutputProtocols().stream().map(PrimaryStorageOutputProtocolRefVO::getOutputProtocol).collect(Collectors.toList());
defaultProtocol = lvo.getDefaultProtocol();
Expand All @@ -68,6 +71,35 @@ public static ExternalPrimaryStorageInventory valueOf(ExternalPrimaryStorageVO l
return new ExternalPrimaryStorageInventory(lvo);
}

private static void desensitizeConfig(Map config) {
if (config == null) return;
desensitizeUrlList(config, "mdsUrls");
desensitizeUrlList(config, "mdsInfos");
}

private static void desensitizeUrlList(Map config, String key) {
Object urls = config.get(key);
if (urls instanceof List) {
List<String> desensitized = new ArrayList<>();
for (Object url : (List) urls) {
desensitized.add(desensitizeUrl(String.valueOf(url)));
}
config.put(key, desensitized);
}
Comment on lines +80 to +88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the target file and examine its context
find . -name "ExternalPrimaryStorageInventory.java" -type f

Repository: MatheMatrix/zstack

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Search for mdsInfos field definition and usage
rg -n "mdsInfos" --type=java -B2 -A2

Repository: MatheMatrix/zstack

Length of output: 5508


🏁 Script executed:

#!/bin/bash
# Search for desensitizeUrlList usage
rg -n "desensitizeUrlList" --type=java -B3 -A3

Repository: MatheMatrix/zstack

Length of output: 1570


🏁 Script executed:

#!/bin/bash
# Search for the method in context to see what keys are being desensitized
rg -n "desensitizeUrlList|desensitizeUrl" --type=java -B5 -A5

Repository: MatheMatrix/zstack

Length of output: 3666


🏁 Script executed:

#!/bin/bash
# Find MdsInfo class definition
rg -n "class MdsInfo" --type=java -A15

Repository: MatheMatrix/zstack

Length of output: 1588


🏁 Script executed:

#!/bin/bash
# Search for MdsInfo implementation/definition with fields
fd "MdsInfo.java" -type f

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Search for where config Map is created/populated with mdsInfos
rg -n "config.*mdsInfos|mdsInfos.*config" --type=java -B3 -A3

Repository: MatheMatrix/zstack

Length of output: 1031


🏁 Script executed:

#!/bin/bash
# Search for the full MdsInfo class to understand it better
cat ./plugin/zbs/src/main/java/org/zstack/storage/zbs/MdsInfo.java

Repository: MatheMatrix/zstack

Length of output: 2399


🏁 Script executed:

#!/bin/bash
# Search for how the config Map is created and what it contains
rg -n "addonConfig|JSONObject.*mdsInfos|mdsInfos" --type=java -B5 -A5 | head -100

Repository: MatheMatrix/zstack

Length of output: 10639


🏁 Script executed:

#!/bin/bash
# Look at the entire ExternalPrimaryStorageInventory file to understand the context
cat ./header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java | head -100

Repository: MatheMatrix/zstack

Length of output: 3013


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or examples showing the config structure
rg -n "mdsInfos.*Map|config.*mdsInfos" --type=java -B3 -A10 | head -80

Repository: MatheMatrix/zstack

Length of output: 2045


🏁 Script executed:

#!/bin/bash
# Search for how JSONObjectUtil converts objects to understand serialization
rg -n "JSONObjectUtil.*toObject.*mdsInfos|toJSON.*mdsInfos" --type=java -B2 -A2

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look for test files that might show the structure of mdsInfos
find . -name "*Test*.java" -o -name "*test*.groovy" | xargs rg -l "mdsInfos|ExternalPrimaryStorage" 2>/dev/null | head -5

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/an/bash
# Check how the config is actually used - look at where ExternalPrimaryStorageInventory is used
rg -n "ExternalPrimaryStorageInventory" --type=java | grep -i "config\|desensitize" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for any serialization or test data showing what mdsInfos looks like
rg -n "\"mdsInfos\"" --type=json -B2 -A5 2>/dev/null | head -50

Repository: MatheMatrix/zstack

Length of output: 44


mdsInfosList<Map> 结构,当前实现会丢失字段。
mdsInfos 序列化后作为 List<LinkedHashMap> 存储在 config 中时,String.valueOf(map) 会将 Map 对象转换成字符串表示(如 {username=..., password=..., addr=...}),导致原有结构丢失,整项被替换为单一字符串。需要区分处理 mdsUrlsList<String>)和 mdsInfosList<Map>),对 Map 类型的列表项保留结构并逐个脱敏其敏感字段。

🔧 参考修复方式(需根据实际字段名调整)
     private static void desensitizeUrlList(Map config, String key) {
         Object urls = config.get(key);
         if (urls instanceof List) {
-            List<String> desensitized = new ArrayList<>();
+            List<Object> desensitized = new ArrayList<>();
             for (Object url : (List) urls) {
-                desensitized.add(desensitizeUrl(String.valueOf(url)));
+                if (url instanceof Map) {
+                    Map itemMap = new LinkedHashMap((Map) url);
+                    if (itemMap.containsKey("username")) {
+                        String username = String.valueOf(itemMap.get("username"));
+                        itemMap.put("username", desensitizeUrl(username + "@dummy").substring(3));
+                    }
+                    if (itemMap.containsKey("password")) {
+                        itemMap.put("password", "***");
+                    }
+                    desensitized.add(itemMap);
+                } else {
+                    desensitized.add(desensitizeUrl(String.valueOf(url)));
+                }
             }
             config.put(key, desensitized);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static void desensitizeUrlList(Map config, String key) {
Object urls = config.get(key);
if (urls instanceof List) {
List<String> desensitized = new ArrayList<>();
for (Object url : (List) urls) {
desensitized.add(desensitizeUrl(String.valueOf(url)));
}
config.put(key, desensitized);
}
private static void desensitizeUrlList(Map config, String key) {
Object urls = config.get(key);
if (urls instanceof List) {
List<Object> desensitized = new ArrayList<>();
for (Object url : (List) urls) {
if (url instanceof Map) {
Map itemMap = new LinkedHashMap((Map) url);
if (itemMap.containsKey("username")) {
String username = String.valueOf(itemMap.get("username"));
itemMap.put("username", desensitizeUrl(username + "@dummy").substring(3));
}
if (itemMap.containsKey("password")) {
itemMap.put("password", "***");
}
desensitized.add(itemMap);
} else {
desensitized.add(desensitizeUrl(String.valueOf(url)));
}
}
config.put(key, desensitized);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java`
around lines 80 - 88, The desensitizeUrlList method currently converts every
list item to a string which destroys List<Map> structures like mdsInfos; update
desensitizeUrlList (and callers handling keys like "mdsUrls" and "mdsInfos") to
detect when config.get(key) is a List of Map (e.g., List<LinkedHashMap>) and in
that case iterate each Map item, desensitizing only the sensitive fields (e.g.,
"username", "password", "addr" or other actual sensitive keys) while preserving
the Map structure, otherwise keep the existing behavior for List<String> by
desensitizing each string via desensitizeUrl and replacing the list in config.
Ensure you reference desensitizeUrlList and handle both "mdsUrls" (List<String>)
and "mdsInfos" (List<Map>) appropriately.

}

private static String desensitizeUrl(String url) {
int atIndex = url.lastIndexOf('@');
if (atIndex > 0) {
int schemeIndex = url.indexOf("://");
if (schemeIndex >= 0 && schemeIndex < atIndex) {
return url.substring(0, schemeIndex + 3) + "***" + url.substring(atIndex);
}
return "***" + url.substring(atIndex);
}
return url;
Comment on lines +91 to +100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant lines
wc -l header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java

Repository: MatheMatrix/zstack

Length of output: 163


🏁 Script executed:

# Read the file around lines 91-100
sed -n '85,105p' header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java

Repository: MatheMatrix/zstack

Length of output: 701


🏁 Script executed:

# Search for existing URL desensitization/masking/sanitization tools
rg -n --type=java -i 'desensit|mask.*url|sanitize.*url|redact.*url' -C 3

Repository: MatheMatrix/zstack

Length of output: 13125


🏁 Script executed:

# Search for any URL related utility classes
fd -e java -type f -exec rg -l 'url.*desensit|url.*mask|url.*sanitize|url.*redact' {} \;

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

# Look at the OtlpExporterProvider implementation
sed -n '40,80p' core/src/main/java/org/zstack/core/telemetry/OtlpExporterProvider.java

Repository: MatheMatrix/zstack

Length of output: 1473


🏁 Script executed:

# Search for more details about how desensitizeUrl is actually used
rg -n 'desensitizeUrl|mdsUrls|mdsInfos' --type java -C 5 header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java

Repository: MatheMatrix/zstack

Length of output: 1118


🏁 Script executed:

# Check if java.net.URI is used anywhere in the codebase for URL parsing
rg -n 'new URI|URI\(' --type java | head -20

Repository: MatheMatrix/zstack

Length of output: 2911


使用 URI 解析而非字符串位置判断来实现 URL 凭据脱敏。

当前基于 lastIndexOf('@') 的实现存在缺陷:若 URL 路径或查询参数中包含 @(如 http://example.com/path@v2),该方法将错误地在路径部分而非凭据部分脱敏。

建议改用 java.net.URIUriComponentsBuilder(如 core/src/main/java/org/zstack/core/telemetry/OtlpExporterProvider.java 中的实现方案)通过正确解析 URL 组件来获取并脱敏用户信息部分。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/storage/addon/primary/ExternalPrimaryStorageInventory.java`
around lines 91 - 100, The current desensitizeUrl implementation in
ExternalPrimaryStorageInventory.desensitizeUrl uses string index checks and
fails when '@' appears in path/query; replace it with proper URI parsing: create
a java.net.URI from the input, call getUserInfo(), and if non-null rebuild the
URI string with the same scheme, userinfo replaced with "***", host, port, path,
query and fragment preserved (use new URI(scheme, "***", host, port, path,
query, fragment).toString() or equivalent handling for authority), and return
the original URL if parsing fails or userInfo is null; ensure edge cases (no
scheme or malformed input) are handled by returning the original url rather than
throwing.

}

public String getIdentity() {
return identity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,9 @@ public void setBootMode(String bootMode) {

public long getRootDiskAllocateSize() {
if (rootDiskOffering == null) {
return this.getImageSpec().getInventory().getSize();
long virtualSize = this.getImageSpec().getInventory().getSize();
long actualSize = this.getImageSpec().getInventory().getActualSize();
return Math.max(virtualSize, actualSize);
}
return rootDiskOffering.getDiskSize();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public enum VmInstanceState {
new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed),
new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying),
new Transaction(VmInstanceStateEvent.running, VmInstanceState.Running),
new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped),
new Transaction(VmInstanceStateEvent.expunging, VmInstanceState.Expunging)
);
Destroyed.transactions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,13 @@ private void handle(APIGetFreeIpMsg msg) {
}
limit -= freeIpInventorys.size();
}

Set<ReservedIpRangeVO> reservedIpRanges = self.getReservedIpRanges();
if (reservedIpRanges != null && !reservedIpRanges.isEmpty()) {
freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream().anyMatch(
r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
}
Comment on lines +1078 to +1083
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

IP 版本不匹配可能导致异常

当前代码在比较 free IP 与 reserved IP range 时,未按 IP 版本过滤。若 free IP 是 IPv4 而 reserved range 是 IPv6(或相反),NetworkUtils.isInRange 内部会调用错误的转换方法(如 ipv4StringToLong 处理 IPv6 地址),可能抛出异常。

参考同文件第 486-487 行的已有模式,应先按 IP 版本过滤 reserved ranges:

🔧 建议修复
         Set<ReservedIpRangeVO> reservedIpRanges = self.getReservedIpRanges();
         if (reservedIpRanges != null && !reservedIpRanges.isEmpty()) {
-            freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream().anyMatch(
-                    r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
+            freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream()
+                    .filter(r -> r.getIpVersion() == freeIp.getIpVersion())
+                    .anyMatch(r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
         }
🤖 Prompt for AI Agents
In `@network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java` around lines
1078 - 1083, The removal logic may call NetworkUtils.isInRange with mismatched
IP versions (IPv4 vs IPv6) causing exceptions; update the block that iterates
self.getReservedIpRanges() and freeIpInventorys (the lambda using freeIp and
reservedIpRanges.stream) to first filter reserved ranges by the same IP version
as freeIp (use whatever helper/method the file uses elsewhere for version
checking, consistent with the pattern around lines ~486-487), then call
NetworkUtils.isInRange only for ranges with matching versions so ipv4/ipv6
conversions are not mixed and exceptions are avoided.


reply.setInventories(freeIpInventorys);

bus.reply(msg, reply);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5446,7 +5446,7 @@ private void deleteSnapshotOnPrimaryStorage(final DeleteSnapshotOnPrimaryStorage
httpCall(DELETE_SNAPSHOT_PATH, cmd, DeleteSnapshotRsp.class, new ReturnValueCompletion<DeleteSnapshotRsp>(msg) {
@Override
public void success(DeleteSnapshotRsp returnValue) {
osdHelper.releaseAvailableCapacity(msg.getSnapshot().getPrimaryStorageInstallPath(), msg.getSnapshot().getSize());
osdHelper.releaseAvailableCapWithRatio(msg.getSnapshot().getPrimaryStorageInstallPath(), msg.getSnapshot().getSize());
bus.reply(msg, reply);
completion.done();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.zstack.network.service.vip.VipVO_;
import org.zstack.tag.PatternedSystemTag;
import org.zstack.tag.TagManager;
import org.zstack.core.upgrade.UpgradeGlobalConfig;
import org.zstack.utils.*;
import org.zstack.utils.function.ForEachFunction;
import org.zstack.utils.logging.CLogger;
Expand Down Expand Up @@ -152,10 +153,22 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti
validate((APIGetCandidateVmNicsForLoadBalancerServerGroupMsg)msg);
} else if (msg instanceof APIChangeLoadBalancerBackendServerMsg) {
validate((APIChangeLoadBalancerBackendServerMsg)msg);
} else if (msg instanceof APIDeleteLoadBalancerMsg) {
validate((APIDeleteLoadBalancerMsg) msg);
}
return msg;
}

private void validate(APIDeleteLoadBalancerMsg msg) {
if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) {
LoadBalancerVO lb = dbf.findByUuid(msg.getUuid(), LoadBalancerVO.class);
if (lb != null && lb.getType() == LoadBalancerType.SLB) {
throw new ApiMessageInterceptionException(argerr(
"cannot delete the standalone load balancer[uuid:%s] during grayscale upgrade", msg.getUuid()));
}
}
}

private void validate(APIDeleteAccessControlListMsg msg) {
/*List<String> refs = Q.New(LoadBalancerListenerACLRefVO.class).select(LoadBalancerListenerACLRefVO_.listenerUuid)
.eq(LoadBalancerListenerACLRefVO_.aclUuid, msg.getUuid()).isNull(LoadBalancerListenerACLRefVO_.serverGroupUuid).listValues();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,34 @@ public void fail(ErrorCode errorCode) {
private void handle(APICreatePortForwardingRuleMsg msg) {
final APICreatePortForwardingRuleEvent evt = new APICreatePortForwardingRuleEvent(msg.getId());

int vipPortEnd = msg.getVipPortEnd() == null ? msg.getVipPortStart() : msg.getVipPortEnd();
int privatePortEnd = msg.getPrivatePortEnd() == null ? msg.getPrivatePortStart() : msg.getPrivatePortEnd();
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return String.format("create-portforwardingrule-vip-%s", msg.getVipUuid());
}

VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class);
@Override
public void run(SyncTaskChain chain) {
int vipPortEnd = msg.getVipPortEnd() == null ? msg.getVipPortStart() : msg.getVipPortEnd();
int privatePortEnd = msg.getPrivatePortEnd() == null ? msg.getPrivatePortStart() : msg.getPrivatePortEnd();

// re-check VIP port overlap under sync to prevent concurrent duplicate rules
boolean overlap = Q.New(PortForwardingRuleVO.class)
.eq(PortForwardingRuleVO_.vipUuid, msg.getVipUuid())
.eq(PortForwardingRuleVO_.protocolType, PortForwardingProtocolType.valueOf(msg.getProtocolType()))
.lte(PortForwardingRuleVO_.vipPortStart, vipPortEnd)
.gte(PortForwardingRuleVO_.vipPortEnd, msg.getVipPortStart())
.isExists();
if (overlap) {
evt.setError(operr(ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10017,
"vip port range[vipStartPort:%s, vipEndPort:%s] overlaps with an existing port forwarding rule on vip[uuid:%s]",
msg.getVipPortStart(), vipPortEnd, msg.getVipUuid()));
bus.publish(evt);
chain.next();
return;
}

VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class);
final PortForwardingRuleVO vo = new PortForwardingRuleVO();
if (msg.getResourceUuid() != null) {
vo.setUuid(msg.getResourceUuid());
Expand Down Expand Up @@ -713,8 +737,8 @@ protected void scripts() {
}
}.execute();

FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName("create-portforwading");
FlowChain flowChain = FlowChainBuilder.newShareFlowChain();
flowChain.setName("create-portforwading");
Comment on lines +740 to +741
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

FlowChain 名称存在拼写错误。

"create-portforwading" 应为 "create-portforwarding"(缺少字母 'r')。

🐛 建议修复
 FlowChain flowChain = FlowChainBuilder.newShareFlowChain();
-flowChain.setName("create-portforwading");
+flowChain.setName("create-portforwarding");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FlowChain flowChain = FlowChainBuilder.newShareFlowChain();
flowChain.setName("create-portforwading");
FlowChain flowChain = FlowChainBuilder.newShareFlowChain();
flowChain.setName("create-portforwarding");
🤖 Prompt for AI Agents
In
`@plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java`
around lines 740 - 741, The FlowChain name in PortForwardingManagerImpl is
misspelled as "create-portforwading"; update the string passed to
flowChain.setName(...) (the FlowChain created via
FlowChainBuilder.newShareFlowChain() and stored in variable flowChain) to the
correct spelling "create-portforwarding" so the chain name is accurate.

VipInventory vipInventory = VipInventory.valueOf(vip);
if (msg.getVmNicUuid() == null) {
ModifyVipAttributesStruct struct = new ModifyVipAttributesStruct();
Expand All @@ -727,13 +751,15 @@ protected void scripts() {
public void success() {
evt.setInventory(PortForwardingRuleInventory.valueOf(vo));
bus.publish(evt);
chain.next();
}

@Override
public void fail(ErrorCode errorCode) {
dbf.remove(vo);
evt.setError(errorCode);
bus.publish(evt);
chain.next();
}
});

Expand All @@ -757,20 +783,22 @@ public void fail(ErrorCode errorCode) {
public void success() {
evt.setInventory(PortForwardingRuleInventory.valueOf(vo));
bus.publish(evt);
chain.next();
}

@Override
public void fail(ErrorCode errorCode) {
dbf.remove(vo);
evt.setError(errorCode);
bus.publish(evt);
chain.next();
}
});

return;
}

chain.then(new ShareFlow() {
flowChain.then(new ShareFlow() {
@Override
public void setup() {
vo.setVmNicUuid(vmNic.getUuid());
Expand Down Expand Up @@ -853,20 +881,29 @@ public void fail(ErrorCode errorCode) {
});


chain.done(new FlowDoneHandler(msg) {
flowChain.done(new FlowDoneHandler(msg) {
@Override
public void handle(Map data) {
evt.setInventory(PortForwardingRuleInventory.valueOf(dbf.reload(vo)));
bus.publish(evt);
chain.next();
}
}).error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
dbf.remove(vo);
evt.setError(errCode);
bus.publish(evt);
chain.next();
}
}).start();
}

@Override
public String getName() {
return String.format("api-create-portforwardingrule-vip-%s", msg.getVipUuid());
}
});
}

private void populateExtensions() {
Expand Down
Loading