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
4f77a78
<fix>[compute]: always submit DeleteVmGC on rollback failure regardle…
AlanJager Feb 12, 2026
1d08048
<chore>[ci]: trigger CI rerun for flaky tests
AlanJager Feb 13, 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 @@ -79,20 +79,25 @@ public void run(MessageReply reply) {
spec.getVmInventory().getUuid(), spec.getVmInventory().getName(),
spec.getDestHost().getUuid(), spec.getDestHost().getName(), reply.getError()));

if (reply.getError().isError(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE)) {
String gcName = String.format("gc-vm-%s-on-host-%s", spec.getVmInventory().getUuid(), spec.getDestHost().getUuid());

DeleteVmGC gc = new DeleteVmGC();
gc.NAME = gcName;
gc.hostUuid = spec.getVmInventory().getHostUuid();
gc.inventory = spec.getVmInventory();
if (gc.existedAndNotCompleted()) {
logger.debug(String.format("There is already a DeleteVmGC of vm[uuid:%s] " +
"on host[uuid:%s], skip.", spec.getVmInventory().getUuid(), spec.getDestHost().getUuid()));
} else {
gc.submit();
}
// ZSTAC-68874: Always submit GC task on rollback failure to clean up VM remnants on host
// Previously only submitted GC when error was GC_ELIGIBLE, but detach PCI failures (e.g., MN unavailable)
// don't return GC_ELIGIBLE, causing GPU resources to remain occupied
String gcName = String.format("gc-vm-%s-on-host-%s", spec.getVmInventory().getUuid(), spec.getDestHost().getUuid());

DeleteVmGC gc = new DeleteVmGC();
gc.NAME = gcName;
gc.hostUuid = spec.getVmInventory().getHostUuid();
gc.inventory = spec.getVmInventory();
if (gc.existedAndNotCompleted()) {
logger.debug(String.format("There is already a DeleteVmGC of vm[uuid:%s] " +
"on host[uuid:%s], skip.", spec.getVmInventory().getUuid(), spec.getDestHost().getUuid()));
} else {
gc.submit();
logger.debug(String.format("Submitted DeleteVmGC for vm[uuid:%s] on host[uuid:%s] due to rollback failure",
spec.getVmInventory().getUuid(), spec.getDestHost().getUuid()));
}

if (!reply.getError().isError(HostErrors.OPERATION_FAILURE_GC_ELIGIBLE)) {
VmTracerCanonicalEvents.OperateFailOnHypervisorData data = new VmTracerCanonicalEvents.OperateFailOnHypervisorData();
data.setHostUuid(spec.getVmInventory().getHostUuid());
data.setVmUuid(spec.getVmInventory().getUuid());
Expand Down
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);
}
}

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;
}

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);
}
Comment on lines 848 to 853
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd 'ImageInventory.java' -t f

Repository: MatheMatrix/zstack

Length of output: 354


🏁 Script executed:

rg -n "getActualSize" header/src/main/java/org/zstack/header/image/ImageInventory.java -A 3

Repository: MatheMatrix/zstack

Length of output: 514


🏁 Script executed:

rg -n "actualSize" header/src/main/java/org/zstack/header/image/ImageInventory.java -B 2 -A 1 | head -30

Repository: MatheMatrix/zstack

Length of output: 519


🏁 Script executed:

rg -n "public Long getSize\(\)|public long getSize\(\)" header/src/main/java/org/zstack/header/image/ImageInventory.java -A 2

Repository: MatheMatrix/zstack

Length of output: 128


修复 getActualSize() 返回类型以避免 NPE 风险。

getActualSize() 返回 Long(可为空)。第 851 行将其自动拆箱到 long 原始类型,若值为 null 则触发 NPE。应将 ImageInventory.getActualSize() 的返回类型从 Long 改为 long,并在 Agent 端确保设置默认值,而非在使用端做空值检查。这与 getSize() 的设计(已正确返回 long)保持一致,也符合 ZStack 关于数值字段的最佳实践。

🤖 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/vm/VmInstanceSpec.java` around lines
848 - 853, getRootDiskAllocateSize() is at risk of NPE because
ImageInventory.getActualSize() currently returns a nullable Long and is
auto-unboxed; change ImageInventory.getActualSize() to return primitive long
instead of Long, update any model/DTO definitions and codegen so the Agent sets
a default value (e.g. 0) when populating ImageInventory, and keep getSize()
as-is; this ensures calls like getImageSpec().getInventory().getActualSize() in
VmInstanceSpec.getRootDiskAllocateSize() cannot NPE.

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 +1079 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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for NetworkUtils.isInRange implementation
rg -n "boolean\s+isInRange\s*\(" -A 15 --type java

Repository: MatheMatrix/zstack

Length of output: 2006


🏁 Script executed:

#!/bin/bash
# Search for ReservedIpRangeVO to check if it has ipVersion field
fd -n "ReservedIpRangeVO" --type java -x cat {} | head -150

Repository: MatheMatrix/zstack

Length of output: 292


🏁 Script executed:

#!/bin/bash
# Check for NetworkUtils methods related to IP validation
rg -n "isIpv4|isIpv6|ipVersion" utils/src/main/java/org/zstack/utils/network/NetworkUtils.java 2>/dev/null | head -30

Repository: MatheMatrix/zstack

Length of output: 1885


🏁 Script executed:

#!/bin/bash
# Look at how reserved ranges are used in comparisons elsewhere
rg -n "ReservedIpRangeVO" -A 5 -B 5 --type java | grep -A 5 -B 5 "isInRange\|ipVersion" | head -50

Repository: MatheMatrix/zstack

Length of output: 4933


reserved IP 过滤需要按 IP 版本匹配,否则触发 IllegalArgumentException 导致 APIGetFreeIp 失败。

当前代码对 self.getReservedIpRanges() 不做版本区分,直接调用 NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())。而 isInRange() 仅根据第一个参数(freeIp)的 IP 版本决定调用 IPv4 或 IPv6 比较方法,若 freeIp 为 IPv4 但 reserved 范围为 IPv6(反之亦然),会导致传递版本不匹配的 startIp/endIp,引发异常。

同一 L3 网络内可同时存在 IPv4 和 IPv6 reserved IP 范围,这里必须按版本过滤。参考同文件 486-487 行的正确做法,应在 anyMatch 前加版本匹配条件:

建议修复
         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 -> {
+                int ipVersion = NetworkUtils.isIpv4Address(freeIp.getIp()) ? IPv6Constants.IPv4 : IPv6Constants.IPv6;
+                return reservedIpRanges.stream().anyMatch(r ->
+                        r.getIpVersion() == ipVersion
+                                && NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp()));
+            });
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java` around lines
1079 - 1083, The reserved-IP filtering block in L3BasicNetwork uses
NetworkUtils.isInRange without ensuring the reserved range and the freeIp share
the same IP version, causing IllegalArgumentException when versions mismatch;
update the lambda in freeIpInventorys.removeIf(...) to only consider
ReservedIpRangeVO r when the IP version of freeIp.getIp() matches
r.getStartIp()/r.getEndIp() (same approach as the check around lines ~486-487),
i.e., first compare IP versions (IPv4 vs IPv6) and only then call
NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp()) so
version-mismatched ranges are skipped.


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()));
}
}
}
Comment on lines +162 to +170
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:

#!/bin/bash

# Check if UpgradeGlobalConfig.GRAYSCALE_UPGRADE exists
echo "=== Checking UpgradeGlobalConfig.GRAYSCALE_UPGRADE ==="
rg "GRAYSCALE_UPGRADE" --type java -B 2 -A 2 | head -20

echo ""
echo "=== Checking LoadBalancerType.SLB ==="
# Search for LoadBalancerType enum definition
rg "enum LoadBalancerType|class LoadBalancerType" --type java -A 20 | head -40

echo ""
echo "=== Checking argerr() pattern in LoadBalancerApiInterceptor ==="
# Get the file path first
fd "LoadBalancerApiInterceptor.java" --type f

Repository: MatheMatrix/zstack

Length of output: 2932


🏁 Script executed:

#!/bin/bash

# Read the LoadBalancerApiInterceptor.java file to see all argerr() calls
fd "LoadBalancerApiInterceptor.java" --type f -x cat {} | grep -n "argerr(" | head -20

Repository: MatheMatrix/zstack

Length of output: 4158


🏁 Script executed:

#!/bin/bash

# Get full context of other argerr calls in the file to see if they use constants
fd "LoadBalancerApiInterceptor.java" --type f -x cat {} | grep -B 2 -A 2 "argerr("

Repository: MatheMatrix/zstack

Length of output: 47032


缺少错误码常量,与文件其他验证方法不一致。

该文件内所有 argerr() 调用都遵循使用 CloudOperationsErrorCode 常量的模式(如 ORG_ZSTACK_NETWORK_SERVICE_LB_10031ORG_ZSTACK_NETWORK_SERVICE_LB_10032 等),但此处直接使用了字符串字面量。需添加对应的错误码常量,以保持一致性并便于错误追踪和国际化。

♻️ 建议修改
 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()));
+                    ORG_ZSTACK_NETWORK_SERVICE_LB_10xxx, "cannot delete the standalone load balancer[uuid:%s] during grayscale upgrade", msg.getUuid()));
         }
     }
 }

需在 CloudOperationsErrorCode 中定义新的错误码常量。

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

In
`@plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java`
around lines 162 - 170, The validate(APIDeleteLoadBalancerMsg msg) method throws
an ApiMessageInterceptionException using argerr with a string literal; change
this to use a CloudOperationsErrorCode constant (matching the file's pattern) so
error codes are consistent—add a new constant (e.g.,
ORG_ZSTACK_NETWORK_SERVICE_LB_xxxxx) in CloudOperationsErrorCode and replace the
literal in the argerr call inside validate (the block checking
UpgradeGlobalConfig.GRAYSCALE_UPGRADE and LoadBalancerVO/LoadBalancerType.SLB)
to reference that new constant.


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 @@ -179,7 +179,10 @@ public List<ActiveVolumeClient> getActiveClients(String installPath, String prot
if (VolumeProtocol.CBD.toString().equals(protocol)) {
GetVolumeClientsCmd cmd = new GetVolumeClientsCmd();
cmd.setPath(installPath);
GetVolumeClientsRsp rsp = syncHttpCall(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class);
GetVolumeClientsRsp rsp = new HttpCaller<>(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class,
null, TimeUnit.SECONDS, 30, true)
.setTryNext(true)
.syncCall();
List<ActiveVolumeClient> clients = new ArrayList<>();

if (!rsp.isSuccess()) {
Expand Down Expand Up @@ -1411,6 +1414,11 @@ public class HttpCaller<T extends AgentResponse> {

private boolean tryNext = false;

HttpCaller<T> setTryNext(boolean tryNext) {
this.tryNext = tryNext;
return this;
}

public HttpCaller(String path, AgentCommand cmd, Class<T> retClass, ReturnValueCompletion<T> callback) {
this(path, cmd, retClass, callback, null, 0, false);
}
Expand Down
Loading