Skip to content

management network IPv6 waterfall M1/M2#4036

Open
zstack-robot-2 wants to merge 14 commits into
feature-5.5.28-IPv6-management-networkfrom
sync/shixin.ruan/shixin-ZSTAC-79206@@5
Open

management network IPv6 waterfall M1/M2#4036
zstack-robot-2 wants to merge 14 commits into
feature-5.5.28-IPv6-management-networkfrom
sync/shixin.ruan/shixin-ZSTAC-79206@@5

Conversation

@zstack-robot-2
Copy link
Copy Markdown
Collaborator

Implement management network IPv6 features from approved waterfall PRD and Func Spec M1/M2. ZSTAC-79206 is used only for branch and Jira association. Joint branch set uses @@5; zstack-distro is standalone.

sync from gitlab !9932

shixin.ruan added 2 commits May 21, 2026 12:36
Allow APIAddHost managementIp to accept IPv6 literals and canonicalize them. Format KVM agent URLs with IPv6 brackets and cover add-host validation with KVM IPv6 case.

Resolves: ZSTAC-79206

Change-Id: I6cdaabc8c7d7d62161565b5df6a02ecc61f787b2
Implement waterfall M1/M2 management network IPv6 support.

Resolves: ZSTAC-79206

Change-Id: Icfeba77200168f8ff05139824166243a87d4b10d
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

该 PR 为管理网络引入 IPv6 支持:新增 IPv6NetworkUtils 和双栈 CIDR 判断,添加 PREFER_IPV6 全局配置,扩展 Platform 的管理 IP/CIDR 解析与持久化,更新 API 校验并在多个插件(KVM、Ceph、REST、NFS、VXLAN)中统一使用 IPv6 友好的格式化/校验,并补充集成测试覆盖。

变更

IPv6 管理网络完整支持

Layer / File(s) Summary
IPv6 网络工具与双栈基础设施
utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java, utils/src/main/java/org/zstack/utils/network/NetworkUtils.java, utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
新增 IPv6NetworkUtils 的规范化、URL/主机/端口格式化与管理端点判定方法;NetworkUtils 新增 isIpInCidr 并在 getNetworkAddressFromCidr 中支持 IPv6;新增若干错误码常量。
管理服务器 IPv6 偏好与平台常量
core/src/main/java/org/zstack/core/NetworkGlobalConfig.java, core/src/main/java/org/zstack/core/Platform.java
新增 NetworkGlobalConfig.PREFER_IPV6 配置以及 Platform 中与 IPv6 相关的导入与常量,为偏好读取与后续实现提供基础。
Platform: CIDR 解析、IP 选择与 JGroups 格式化
core/src/main/java/org/zstack/core/Platform.java
新增按目标 IP 获取管理 CIDR 的实现(ip -4/ip -6),新增管理 server id 的加载/持久化逻辑,新增管理 IP 候选选择/偏好、normalize 与 formatJGroupsInitialHosts 等方法。
API 消息与参数校验中的 IPv6 支持
compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java, plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java, plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsApiParamChecker.java
HostApiInterceptor 在添加主机时使用 isValidManagementEndpoint 并对 IPv6 进行规范化;VxlanPoolApiInterceptor 将远端 VTEP 校验改为通用 IP 校验并更新错误码;NfsApiParamChecker 支持 IPv6 方括号形式的 NFS URL 并改用 isIpInCidr。
REST / KVM / Ceph / ApplianceVm 集成
core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java, plugin/kvm/.../KVMHost.java, plugin/kvm/.../KVMHostFactory.java, plugin/kvm/.../KVMHostUtils.java, plugin/ceph/.../CephBackupStorageMonBase.java, plugin/ceph/.../CephPrimaryStorageBase.java, plugin/ceph/.../CephPrimaryStorageMonBase.java, plugin/applianceVm/.../ApplianceVmConstant.java, plugin/applianceVm/.../ApplianceVmFacadeImpl.java
RESTFacadeImpl 使用 IPv6NetworkUtils 构建 base/callback/command URL;KVM 使用 KVMHostUtils.formatHostForUrl;Ceph 相关改为 IPv6 友好 host:port 格式;ApplianceVm 增加 managementNodeIp6Cidr,并按 VR 管段优先选择管理节点 IP。
IPv6 管理网络集成测试
test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy, test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy
新增集成测试覆盖 PREFER_IPV6 配置与系统属性切换、双栈 IP 选择、HTTP/REST URL 构建(IPv6 方括号)、主机:端口 格式化、IPv6 规范化、管理端点合法性、JGroups 初始 hosts 格式、CIDR 推导与 isIpInCidr 双栈行为、管理 server id 的加载/持久化等。
边界情况修正
utils/src/main/java/org/zstack/utils/zsha2/ZSha2Helper.java
调整 dbvip 的 grep 匹配以识别 CIDR 风格(匹配 dbvip/)。

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 我是小兔子,网中行,
双栈世界添新名;
方括号里套 IPv6,
管理端点更分明;
节点启动稳又宁。


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Title check ❌ Error PR标题未遵循'[scope]: '格式要求,缺少类型前缀与范围标注。 将标题改为规范格式,如'feat[core]: Add IPv6 support for management network'或'feat[mgt-ipv6]: Implement IPv6 waterfall M1/M2'
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed PR描述清晰地说明了变更涉及管理网络IPv6特性的实现,与changeset高度相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/shixin.ruan/shixin-ZSTAC-79206@@5

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java (1)

515-517: 💤 Low value

建议为 normalizeIpv6 添加空值检查以保持一致性。

既然 isIpv6AddressformatHostForUrl 已添加 null 防护,normalizeIpv6 也应处理 null 输入以保持 API 一致性。当前如果传入 null,IPv6Address.fromString(null) 会抛出异常。

♻️ 建议的改进
 public static String normalizeIpv6(String ip) {
+    if (ip == null) {
+        return null;
+    }
     return getIpv6AddressCanonicalString(ip);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java` around
lines 515 - 517, normalizeIpv6 currently forwards to
getIpv6AddressCanonicalString without null protection, which lets
IPv6Address.fromString(null) throw; add a null check at the start of
normalizeIpv6 (if ip == null) return null to match the null-safe behavior of
isIpv6Address and formatHostForUrl, then otherwise call
getIpv6AddressCanonicalString(ip).
plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java (1)

138-140: ⚡ Quick win

建议为公共方法添加 Javadoc 文档。

此方法是公共 API,应当配有 Javadoc 说明其用途、参数和返回值。特别是对于处理 IPv6 地址格式化这种非平凡逻辑,文档应说明:

  • 输入参数的预期格式(IPv4、IPv6 或已括号化的 IPv6)
  • 返回值的格式(IPv4 原样、IPv6 添加方括号)
  • 是否对已括号化的 IPv6 幂等
📝 建议添加的 Javadoc
+    /**
+     * Format a host address (IPv4 or IPv6) for use in URLs.
+     * IPv6 addresses are wrapped in square brackets; IPv4 addresses are returned unchanged.
+     * Already-bracketed IPv6 addresses are returned as-is (idempotent).
+     *
+     * `@param` host the host address (IPv4, IPv6, or bracketed IPv6)
+     * `@return` the URL-safe host format
+     */
     public static String formatHostForUrl(String host) {
         return IPv6NetworkUtils.formatHostForUrl(host);
     }

根据编码规范:接口方法与公共方法应当配有有效的 Javadoc 注释。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java` around lines 138 -
140, Add a Javadoc block to the public method formatHostForUrl explaining its
purpose and contract: state that it formats a host string for use in URLs by
delegating to IPv6NetworkUtils.formatHostForUrl, document the expected input
formats (IPv4, IPv6, and already-bracketed IPv6), describe the return value
(IPv4 returned unchanged, IPv6 returned with surrounding brackets), and note
idempotence (calling formatHostForUrl on an already-bracketed IPv6 is a no-op).
Include `@param` host and `@return` tags and a short example for IPv4 vs IPv6 to
clarify behavior, referencing the formatHostForUrl method name and the
delegation to IPv6NetworkUtils.formatHostForUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java`:
- Around line 127-134: In validate(APIAddHostMsg msg) trim the incoming
management IP first and operate on the trimmed value: retrieve
msg.getManagementIp(), null-check it, call String.trim() and set it back into
msg (e.g., msg.setManagementIp(trimmed)), then call
isValidManagementEndpoint(trimmed) and later pass the trimmed value to
IPv6NetworkUtils.getIpv6AddressCanonicalString; update references in the
validate method so validation and normalization always use the trimmed
managementIp.

In `@core/src/main/java/org/zstack/core/Platform.java`:
- Around line 825-834: The current getManagementServerCidr(int) can pick an IP
from any enabled NIC (first match) instead of the IP bound to the management
interface; update the logic so when the primary current IP doesn't match the
requested ipVersion you fetch the management-interface-specific address for that
version (use the management-bound getters getManagementServerIp6() /
getManagementServerIp4() or a helper that returns the IP tied to the configured
management NIC) and then call getManagementServerCidr(ip) with that management
interface IP; ensure you do not iterate over all NIC addresses or return the
first version-matching address from the system interfaces.

In
`@plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java`:
- Around line 58-60: Trim and canonicalize the RemoteVtepIp before validation
and before any string equality checks: in
validate(APICreateVxlanPoolRemoteVtepMsg msg) (and the analogous places
referenced around the same methods), replace direct uses of
msg.getRemoteVtepIp() with a normalized value obtained by trimming whitespace
and converting to canonical form (e.g. using
InetAddress.getByName(trimmed).getHostAddress() or a
NetworkUtils.normalize/canonicalize helper), use that normalized IP for
NetworkUtils.isIpAddress validation, for duplicate detection, for error
messages, and write the normalized IP back into the message or use it
consistently so IPv6 equivalent forms no longer bypass checks or break
deletions.

---

Nitpick comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java`:
- Around line 138-140: Add a Javadoc block to the public method formatHostForUrl
explaining its purpose and contract: state that it formats a host string for use
in URLs by delegating to IPv6NetworkUtils.formatHostForUrl, document the
expected input formats (IPv4, IPv6, and already-bracketed IPv6), describe the
return value (IPv4 returned unchanged, IPv6 returned with surrounding brackets),
and note idempotence (calling formatHostForUrl on an already-bracketed IPv6 is a
no-op). Include `@param` host and `@return` tags and a short example for IPv4 vs
IPv6 to clarify behavior, referencing the formatHostForUrl method name and the
delegation to IPv6NetworkUtils.formatHostForUrl.

In `@utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java`:
- Around line 515-517: normalizeIpv6 currently forwards to
getIpv6AddressCanonicalString without null protection, which lets
IPv6Address.fromString(null) throw; add a null check at the start of
normalizeIpv6 (if ip == null) return null to match the null-safe behavior of
isIpv6Address and formatHostForUrl, then otherwise call
getIpv6AddressCanonicalString(ip).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: bef24941-3ca9-49fa-a22c-0fc1c94ce185

📥 Commits

Reviewing files that changed from the base of the PR and between 3330ffa and 5b72dc4.

📒 Files selected for processing (20)
  • compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java
  • core/src/main/java/org/zstack/core/NetworkGlobalConfig.java
  • core/src/main/java/org/zstack/core/Platform.java
  • core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java
  • plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
  • plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/CephBackupStorageMonBase.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsApiParamChecker.java
  • plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java
  • test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
  • utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java
  • utils/src/main/java/org/zstack/utils/network/NetworkUtils.java
  • utils/src/main/java/org/zstack/utils/zsha2/ZSha2Helper.java

Comment on lines 127 to 134
private void validate(APIAddHostMsg msg) {
if (!NetworkUtils.isIpv4Address(msg.getManagementIp()) && !NetworkUtils.isHostname(msg.getManagementIp())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10113, "managementIp[%s] is neither an IPv4 address nor a valid hostname", msg.getManagementIp()));
if (!isValidManagementEndpoint(msg.getManagementIp())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10128, INVALID_MANAGEMENT_IP_ERROR, msg.getManagementIp()));
}

if (IPv6NetworkUtils.isIpv6Address(msg.getManagementIp())) {
msg.setManagementIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(msg.getManagementIp()));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

先 trim 再做管理地址校验与规范化

Line 128/132 直接使用原始 managementIp。若用户复制粘贴带空格/换行,会误判为非法或写入非预期值。

💡 建议修改
 private void validate(APIAddHostMsg msg) {
-    if (!isValidManagementEndpoint(msg.getManagementIp())) {
-        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10128, INVALID_MANAGEMENT_IP_ERROR, msg.getManagementIp()));
+    String managementIp = msg.getManagementIp() == null ? null : msg.getManagementIp().trim();
+    msg.setManagementIp(managementIp);
+    if (!isValidManagementEndpoint(managementIp)) {
+        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10128, INVALID_MANAGEMENT_IP_ERROR, managementIp));
     }
 
-    if (IPv6NetworkUtils.isIpv6Address(msg.getManagementIp())) {
-        msg.setManagementIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(msg.getManagementIp()));
+    if (IPv6NetworkUtils.isIpv6Address(managementIp)) {
+        msg.setManagementIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(managementIp));
     }
 }

As per coding guidelines "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java` around
lines 127 - 134, In validate(APIAddHostMsg msg) trim the incoming management IP
first and operate on the trimmed value: retrieve msg.getManagementIp(),
null-check it, call String.trim() and set it back into msg (e.g.,
msg.setManagementIp(trimmed)), then call isValidManagementEndpoint(trimmed) and
later pass the trimmed value to IPv6NetworkUtils.getIpv6AddressCanonicalString;
update references in the validate method so validation and normalization always
use the trimmed managementIp.

Comment thread core/src/main/java/org/zstack/core/Platform.java
Comment on lines +58 to +60
private void validate(APICreateVxlanPoolRemoteVtepMsg msg) {
boolean isIpv4 = NetworkUtils.isIpv4Address(msg.getRemoteVtepIp());
if (!isIpv4) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10015, "%s:is not ipv4", msg.getRemoteVtepIp()));
if (!NetworkUtils.isIpAddress(msg.getRemoteVtepIp())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10015, "%s is not a valid IP address", msg.getRemoteVtepIp()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

IPv6 未规范化会绕过重复校验并影响删除匹配

Line 66 依赖字符串精确匹配,但 Line 59/75 仅做“是否是 IP”校验。IPv6 不同等价写法会被当成不同值,导致重复数据或删除不到目标记录。

💡 建议修改
+import org.zstack.utils.network.IPv6NetworkUtils;
+
 private void validate(APICreateVxlanPoolRemoteVtepMsg msg) {
-    if (!NetworkUtils.isIpAddress(msg.getRemoteVtepIp())) {
-        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10015, "%s is not a valid IP address", msg.getRemoteVtepIp()));
+    String remoteVtepIp = msg.getRemoteVtepIp() == null ? null : msg.getRemoteVtepIp().trim();
+    if (IPv6NetworkUtils.isIpv6Address(remoteVtepIp)) {
+        remoteVtepIp = IPv6NetworkUtils.getIpv6AddressCanonicalString(remoteVtepIp);
+    }
+    msg.setRemoteVtepIp(remoteVtepIp);
+    if (!NetworkUtils.isIpAddress(remoteVtepIp)) {
+        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10015, "%s is not a valid IP address", remoteVtepIp));
     }
     ...
 }
 
 private void validate(APIDeleteVxlanPoolRemoteVtepMsg msg) {
-    if (!NetworkUtils.isIpAddress(msg.getRemoteVtepIp())) {
-        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10017, "%s is not a valid IP address", msg.getRemoteVtepIp()));
+    String remoteVtepIp = msg.getRemoteVtepIp() == null ? null : msg.getRemoteVtepIp().trim();
+    if (IPv6NetworkUtils.isIpv6Address(remoteVtepIp)) {
+        remoteVtepIp = IPv6NetworkUtils.getIpv6AddressCanonicalString(remoteVtepIp);
+    }
+    msg.setRemoteVtepIp(remoteVtepIp);
+    if (!NetworkUtils.isIpAddress(remoteVtepIp)) {
+        throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10017, "%s is not a valid IP address", remoteVtepIp));
     }
 }

As per coding guidelines "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"。

Also applies to: 63-67, 75-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java`
around lines 58 - 60, Trim and canonicalize the RemoteVtepIp before validation
and before any string equality checks: in
validate(APICreateVxlanPoolRemoteVtepMsg msg) (and the analogous places
referenced around the same methods), replace direct uses of
msg.getRemoteVtepIp() with a normalized value obtained by trimming whitespace
and converting to canonical form (e.g. using
InetAddress.getByName(trimmed).getHostAddress() or a
NetworkUtils.normalize/canonicalize helper), use that normalized IP for
NetworkUtils.isIpAddress validation, for duplicate detection, for error
messages, and write the normalized IP back into the message or use it
consistently so IPv6 equivalent forms no longer bypass checks or break
deletions.

Use new VXLAN error code constants for remote VTEP invalid IP validation so the new messages do not reuse existing ErrorCodes.

Resolves: ZSTAC-79206

Change-Id: Ibd6ea314eda79a25e56f3e75d13e0101ca0c4b44
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java (2)

58-72: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

缺少 trim 和 IPv6 规范化导致重复校验失效与数据一致性问题

Line 59 仅校验 IP 格式但未规范化,Line 66 依赖字符串精确匹配。存在两个严重问题:

  1. 未对输入参数执行 trim 操作:违反编码规范要求"注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"
  2. IPv6 地址未规范化:不同等价写法(如 2001:db8::12001:0db8:0000:0000:0000:0000:0000:0001)会被当作不同值,导致:
    • 重复校验失效,可创建重复的远端 VTEP 记录
    • 数据库中存储非标准格式的 IPv6 地址
    • 后续删除操作可能因格式不匹配而失败
🔧 建议的修复方案
+import org.zstack.utils.network.IPv6NetworkUtils;
+
 private void validate(APICreateVxlanPoolRemoteVtepMsg msg) {
+    String remoteVtepIp = msg.getRemoteVtepIp();
+    if (remoteVtepIp != null) {
+        remoteVtepIp = remoteVtepIp.trim();
+        if (IPv6NetworkUtils.isIpv6Address(remoteVtepIp)) {
+            remoteVtepIp = IPv6NetworkUtils.getIpv6AddressCanonicalString(remoteVtepIp);
+        }
+        msg.setRemoteVtepIp(remoteVtepIp);
+    }
     if (!NetworkUtils.isIpAddress(msg.getRemoteVtepIp())) {
         throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10032, "%s is not a valid IP address", msg.getRemoteVtepIp()));
     }

As per coding guidelines "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java`
around lines 58 - 72, Trim and normalize the remote VTEP IP before validation
and DB queries in validate(APICreateVxlanPoolRemoteVtepMsg msg): take
msg.getRemoteVtepIp(), apply trim, parse it as an InetAddress and obtain a
canonical textual form (e.g. InetAddress.getByName(...).getHostAddress()) to
normalize IPv6, then use that normalizedIp for NetworkUtils.isIpAddress check
and for the SimpleQuery condition on VtepVO_.vtepIp; also ensure the normalized
value is what gets persisted (or set back on the message if mutable) so stored
and comparison values are consistent.

74-79: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

缺少 trim 和 IPv6 规范化导致删除操作可能失败

Line 75 存在与创建校验相同的问题:

  1. 未对输入参数执行 trim 操作:违反编码规范
  2. IPv6 地址未规范化:如果数据库中存储的是 IPv6 地址的某种格式(如 2001:db8::1),而用户提供等价但不同的格式(如 2001:0db8:0000:0000:0000:0000:0000:0001),删除操作将因字符串不匹配而无法找到目标记录
🔧 建议的修复方案
 private void validate(APIDeleteVxlanPoolRemoteVtepMsg msg) {
+    String remoteVtepIp = msg.getRemoteVtepIp();
+    if (remoteVtepIp != null) {
+        remoteVtepIp = remoteVtepIp.trim();
+        if (IPv6NetworkUtils.isIpv6Address(remoteVtepIp)) {
+            remoteVtepIp = IPv6NetworkUtils.getIpv6AddressCanonicalString(remoteVtepIp);
+        }
+        msg.setRemoteVtepIp(remoteVtepIp);
+    }
     if (!NetworkUtils.isIpAddress(msg.getRemoteVtepIp())) {
         throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_VXLAN_VXLANNETWORKPOOL_10033, "%s is not a valid IP address", msg.getRemoteVtepIp()));
     }
 }

As per coding guidelines "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java`
around lines 74 - 79, In validate(APIDeleteVxlanPoolRemoteVtepMsg msg) inside
VxlanPoolApiInterceptor, trim the incoming msg.getRemoteVtepIp() and replace the
raw value with a canonical/normalized IP string before validation and DB lookup;
specifically, perform msg.setRemoteVtepIp(msg.getRemoteVtepIp().trim()) and then
normalize IPv6 using the existing NetworkUtils normalization helper (e.g.,
NetworkUtils.normalizeIpAddress or equivalent canonicalization method) so
NetworkUtils.isIpAddress() checks the cleaned value and deletion will match
stored IPv6 canonical forms.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java`:
- Around line 58-72: Trim and normalize the remote VTEP IP before validation and
DB queries in validate(APICreateVxlanPoolRemoteVtepMsg msg): take
msg.getRemoteVtepIp(), apply trim, parse it as an InetAddress and obtain a
canonical textual form (e.g. InetAddress.getByName(...).getHostAddress()) to
normalize IPv6, then use that normalizedIp for NetworkUtils.isIpAddress check
and for the SimpleQuery condition on VtepVO_.vtepIp; also ensure the normalized
value is what gets persisted (or set back on the message if mutable) so stored
and comparison values are consistent.
- Around line 74-79: In validate(APIDeleteVxlanPoolRemoteVtepMsg msg) inside
VxlanPoolApiInterceptor, trim the incoming msg.getRemoteVtepIp() and replace the
raw value with a canonical/normalized IP string before validation and DB lookup;
specifically, perform msg.setRemoteVtepIp(msg.getRemoteVtepIp().trim()) and then
normalize IPv6 using the existing NetworkUtils normalization helper (e.g.,
NetworkUtils.normalizeIpAddress or equivalent canonicalization method) so
NetworkUtils.isIpAddress() checks the cleaned value and deletion will match
stored IPv6 canonical forms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: cff868de-edf8-4a5f-925c-9c37a2f8266d

📥 Commits

Reviewing files that changed from the base of the PR and between 5b72dc4 and 5cdd43a.

📒 Files selected for processing (2)
  • plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanPoolApiInterceptor.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

Select appliance VM bootstrap managementNodeIp by VR management CIDR and reject non-routable IPv6 management endpoints.

Resolves: ZSTAC-79206

Change-Id: If4f857d07b043519fc2f6a7b752246f35d0ced8e
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy (1)

43-45: ⚡ Quick win

方法名称与实现不符,建议重命名或补充断言

方法名 testPreferIpv6DefaultFalse() 暗示会测试默认值为 false,但实际只校验了配置项的 identity 字符串。建议:

  1. 重命名为 testPreferIpv6Identity() 以准确反映其意图
  2. 或添加 assert !Platform.isManagementServerPreferIpv6() 来实际测试默认值
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy`
around lines 43 - 45, The test method name testPreferIpv6DefaultFalse() doesn't
match its implementation (it only asserts
NetworkGlobalConfig.PREFER_IPV6.getIdentity()); either rename the method to
testPreferIpv6Identity() to reflect that it checks the identity string, or add
an assertion to test the actual default value by calling
Platform.isManagementServerPreferIpv6() (e.g., assert
!Platform.isManagementServerPreferIpv6()) so the method name and behavior align;
update the method name or add the extra assertion in the
testPreferIpv6DefaultFalse / testPreferIpv6Identity method accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy`:
- Around line 43-45: The test method name testPreferIpv6DefaultFalse() doesn't
match its implementation (it only asserts
NetworkGlobalConfig.PREFER_IPV6.getIdentity()); either rename the method to
testPreferIpv6Identity() to reflect that it checks the identity string, or add
an assertion to test the actual default value by calling
Platform.isManagementServerPreferIpv6() (e.g., assert
!Platform.isManagementServerPreferIpv6()) so the method name and behavior align;
update the method name or add the extra assertion in the
testPreferIpv6DefaultFalse / testPreferIpv6Identity method accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: a0ef1b22-dd4a-4c6a-921b-35beca761d91

📥 Commits

Reviewing files that changed from the base of the PR and between 5cdd43a and fb79492.

📒 Files selected for processing (6)
  • core/src/main/java/org/zstack/core/NetworkGlobalConfig.java
  • core/src/main/java/org/zstack/core/Platform.java
  • plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
  • test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy
  • utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java

shixin.ruan added 2 commits May 21, 2026 19:50
Persist managementServerId across management IP changes and add IPv6-safe REST URL helpers with focused coverage.

Resolves: ZSTAC-79206

Change-Id: I03d57df9dd199aba5875f1726cb5cc1cc4b1e32e
Add focused coverage for IPv4 and IPv6 management CIDR parsing from ip address command output.

Resolves: ZSTAC-79206

Change-Id: Id3776bc971679c3e1b88f14b1f8a19ced6621098
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
core/src/main/java/org/zstack/core/Platform.java (1)

889-897: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

把 IPv4/IPv6 回退地址绑定到管理网卡。

Line 896 在协议族不匹配时会回退到 getManagementServerIp6() / getManagementServerIp4(),但 Line 947-984 是扫描所有已启用网卡并返回第一个匹配地址,不是当前管理 IP 所在接口的地址。多网卡场景下,getManagementServerCidr(int)getManagementServerIp6Cidr()getManagementServerIps() 都可能拿到非管理网段的地址/网段,导致下游传播错误的 management CIDR/IP。

Also applies to: 947-999

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/main/java/org/zstack/core/Platform.java` around lines 889 - 897, The
fallback in getManagementServerCidr(int) uses
getManagementServerIp6()/getManagementServerIp4() which scans all NICs and
returns the first matching address, potentially from a different interface than
the current management IP; change the fallback logic to resolve the
opposite-family address on the same network interface as the current management
IP (i.e. locate the NIC/interface owning getManagementServerIp(), then look up
its IPv6/IPv4 address and compute CIDR from that address). Apply the same
interface-bound lookup strategy to the related helpers
getManagementServerIp6Cidr() and getManagementServerIps() so they always return
addresses/CIDRs from the management IP’s interface rather than any enabled NIC.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/src/main/java/org/zstack/core/Platform.java`:
- Around line 512-513: 当前实现调用 loadOrCreateManagementServerId(并使用
Platform::getUuid)在静态初始化时会回写 classpath 下的
zstack.properties,从而把只读配置路径变为可写并可能覆盖用户文件;请修改该逻辑使其不再直接写回 zstack.properties:在
loadOrCreateManagementServerId 中先尝试从 classpath 读取 managementServerId,若缺失则生成新的
ID(Platform::getUuid)但将运行时状态持久化到一个专用的状态文件/目录(例如管理服务器状态文件而非
zstack.properties),并确保写操作只针对该状态文件;同时检查并修复代码中其它类似写回 classpath
配置的地方(与本处相同的逻辑调用点),保证平台启动不会修改或覆盖原始 zstack.properties。

In
`@test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy`:
- Around line 160-176: The test method testManagementServerIdPersisted reads but
does not clear the JVM system property Platform.MANAGEMENT_SERVER_ID_PROPERTY
before calling Platform.loadOrCreateManagementServerId, which can make
generatedId come from an existing environment; modify the test to clear
System.clearProperty(Platform.MANAGEMENT_SERVER_ID_PROPERTY) (or remove it)
before the first call to Platform.loadOrCreateManagementServerId and ensure the
original value (oldValue) is restored in the finally block so the test is
isolated; refer to the testManagementServerIdPersisted method and the
Platform.loadOrCreateManagementServerId / Platform.MANAGEMENT_SERVER_ID_PROPERTY
symbols when implementing the change.

---

Duplicate comments:
In `@core/src/main/java/org/zstack/core/Platform.java`:
- Around line 889-897: The fallback in getManagementServerCidr(int) uses
getManagementServerIp6()/getManagementServerIp4() which scans all NICs and
returns the first matching address, potentially from a different interface than
the current management IP; change the fallback logic to resolve the
opposite-family address on the same network interface as the current management
IP (i.e. locate the NIC/interface owning getManagementServerIp(), then look up
its IPv6/IPv4 address and compute CIDR from that address). Apply the same
interface-bound lookup strategy to the related helpers
getManagementServerIp6Cidr() and getManagementServerIps() so they always return
addresses/CIDRs from the management IP’s interface rather than any enabled NIC.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 6a8d4a72-d14c-4a9b-9982-2dad6478b8cb

📥 Commits

Reviewing files that changed from the base of the PR and between fb79492 and 0f603cc.

📒 Files selected for processing (3)
  • core/src/main/java/org/zstack/core/Platform.java
  • core/src/main/java/org/zstack/core/rest/RESTFacadeImpl.java
  • test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy

Comment on lines +512 to +513
// get ms id should after global property setup
msId = loadOrCreateManagementServerId(globalPropertiesFile, Platform::getUuid);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

不要在启动时回写 zstack.properties

如果 managementServerId 缺失,这里会在静态初始化阶段把新值写回 classpath 上的 zstack.properties。这会把原本“只读加载配置”的启动路径变成“必须能修改配置文件”:只读部署、镜像内置配置或受外部配置管理托管的场景都会直接启动失败,而且还会重写用户维护的主配置文件。更稳妥的是把这个运行时状态落到专用状态文件,而不是回写主配置。
As per coding guidelines, "向后兼容原则 之前的代码产生的行为不要直接去改动,即使是 bug 也可能用户有依赖,不应直接改动" and "平台不应直接覆盖用户可能自定义修改过的东西,这个非常重要".

Also applies to: 720-745, 760-775

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/main/java/org/zstack/core/Platform.java` around lines 512 - 513,
当前实现调用 loadOrCreateManagementServerId(并使用 Platform::getUuid)在静态初始化时会回写 classpath
下的 zstack.properties,从而把只读配置路径变为可写并可能覆盖用户文件;请修改该逻辑使其不再直接写回 zstack.properties:在
loadOrCreateManagementServerId 中先尝试从 classpath 读取 managementServerId,若缺失则生成新的
ID(Platform::getUuid)但将运行时状态持久化到一个专用的状态文件/目录(例如管理服务器状态文件而非
zstack.properties),并确保写操作只针对该状态文件;同时检查并修复代码中其它类似写回 classpath
配置的地方(与本处相同的逻辑调用点),保证平台启动不会修改或覆盖原始 zstack.properties。

shixin.ruan added 8 commits May 21, 2026 20:01
Add focused management IPv6 coverage for NFS URL parsing, Ceph mon URL parsing, and VXLAN VTEP validation.

Resolves: ZSTAC-79206

Change-Id: Ia2925ec043a3b2be4d1e3b921ad285a8aebc9aed
Add focused IPv6 coverage for KVM extra IP CIDR selection used by data and migration network paths.

Resolves: ZSTAC-79206

Change-Id: I03ea5987d0e4ebc4fdd78dc261297baaf12d0832
Add focused ApplianceVm IPv6 bootstrap JSON tests for MN IP and CIDR fields.

Resolves: ZSTAC-79206

Change-Id: Icdbe40c69af9306eb2663a12031774c1b4c64195
Avoid lambda bytecode that blocks KvmHostIpv6Case initialization before IPv6 assertions.

Resolves: ZSTAC-79206

Change-Id: I2d1fdced05d1127f6414e4ae0adc491e10b23d7d
Add focused coverage for host management IPv6 canonicalization and invalid endpoint detection without starting the full KVM Spring harness.

Resolves: ZSTAC-79206

Change-Id: I731c2168a22e5b52f993319f21835ca9a456a76b
Add API and DB assertions for IPv6 remote VXLAN VTEP creation and invalid remote VTEP rejection.

Resolves: ZSTAC-79206

Change-Id: Ieb2368b5f4a274bcad5b192003ac051ae4179552
Avoid relying only on Configurable injection for GlobalConfig instances created during GlobalConfigFacadeImpl startup.

Resolves: ZSTAC-79206

Change-Id: I3f975fc23240d88ba08dc870220277cc065f7cfb
Make startup-only objects use explicit facades instead of AspectJ injection during unit tests.\n\nMove allocator and VM flow extension lookup to runtime to avoid early plugin registry access.\n\nAllow IPv6 remote VTEP addresses through API and SDK validation and cover host/VTEP IPv6 cases.\n\nVerification:\n- mvn -Dtest=KvmHostIpv6Case test\n- mvn -Dtest=AddRemoteVxlanVtepIpCase test\n- ./runMavenProfile premium

Change-Id: I0146cfab99160e1f784170d051444e4fec69ad83
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant