Skip to content

5.5.22 unified-hardware QA bug batch: 85182/85184/85190/85350#4034

Open
MatheMatrix wants to merge 9 commits into
feature-unifi-hostfrom
sync/jin.ma/cordon-container@@2
Open

5.5.22 unified-hardware QA bug batch: 85182/85184/85190/85350#4034
MatheMatrix wants to merge 9 commits into
feature-unifi-hostfrom
sync/jin.ma/cordon-container@@2

Conversation

@MatheMatrix
Copy link
Copy Markdown
Owner

Scope

Fixes 4 of the 5 jiras under epic ZSTAC-85170 (5.5.22-Cloud-硬件统一管理-Buglist). The 5th (ZSTAC-85334) lives in premium baremetal2 — see companion MR on premium repo (same source branch).

Commits in this MR

Commit Type Jira Summary
ab995e3edc refactor ZSTAC-85182 move PasswordConverter + EncryptFacade interface from core to header.core.encrypt so header-resident VOs can use @Convert(PasswordConverter.class) (the same mechanism KVMHostVO uses). EncryptFacade.isEncryptionDisabled() absorbs the previous EncryptGlobalConfig.value() check so the converter has zero core deps. ~30 import-rename sites across kvm/ceph/sftp/test.
d15368f77f fix ZSTAC-85182 annotate PhysicalServerAO.oobPassword with @Convert(PasswordConverter.class) and drop the application-layer PhysicalServerOobPasswordCrypto stop-gap. Hibernate handles encrypt-on-write / decrypt-on-read transparently. IT case rewritten to mirror HostPasswordEncryptCase (state assertion via EncryptEntityMetadataVO).
a3bdf52fcd fix ZSTAC-85184 / ZSTAC-85190 / ZSTAC-85350 actionable interceptor errors in PhysicalServerApiInterceptor: pre-check zone+serialNumber uniqueness; pre-check (serverUuid, roleType) uniqueness; reject GATEWAY_PXE with missing DHCP wiring. All use 3-arg argerr(globalErrorCode, fmt, args...) so the user-facing details shows a fully formatted sentence instead of just the offending uuid.

Integration tests

  • PhysicalServerOobPasswordEncryptCase (test/) — 3 sub-cases PASSED
  • PhysicalServerInterceptorErrorsCase (test/) — 3 sub-cases PASSED

E2E (MN 172.24.226.204)

Hot-deployed all touched jars + xmls; verified on real MN:

  • 85182: input E2E-Final-Refactor-Pwd! → DB oobPassword = 64-byte ciphertext RoPHbrg24uuy54aCjuInm30ZbOtwg21o... (encryption=LocalEncryption); decrypt round-trip matches.
  • 85184: duplicate serialNumber → details: PhysicalServer with serialNumber[TC-PS-01] already exists in Zone[uuid:...]; serialNumber must be unique per zone.
  • 85190: duplicate role attach → details: PhysicalServer[uuid:...] already has role[type:KVM_HOST] attached; detach first or pick a different roleType.
  • 85350: GATEWAY_PXE missing DHCP → details: ProvisionNetwork[type:GATEWAY_PXE] requires DHCP wiring; missing field(s): dhcpInterface, dhcpRangeStartIp, dhcpRangeEndIp, dhcpRangeNetmask.

MN rolled back to baseline (test PSes deleted, encrypt config restored to None).

Companion MR

Premium side (ZSTAC-85334 + the cross-repo refactor sweep) is on the matching cordon-container@@2 branch in zstackio/premium.

Resolves: ZSTAC-85182, ZSTAC-85184, ZSTAC-85190, ZSTAC-85350

sync from gitlab !9930

MaJin1996 added 3 commits May 19, 2026 12:03
Scanner/PowerTracker static overrides return ShellResult
instead of pre-classified Status, so prod PowerStatusParser
and isAuthFailure classifier run on the mocked stdout/stderr.

Drop TestGatewayPxeProvisionProvider + spring XML + the two
PhysicalServerOpsCase provision cases. OSS contract assertions
jobResult.contains(server/network uuid) moved to premium
happy path; STANDALONE_PXE reserved-stub case removed.

Resolves: ZSTAC-84191

Change-Id: I6fcf4a29c55553e20ff5517540cbf563e753b543
PRD §2.5.1 requires PhysicalServerVO.serialNumber / manufacturer
/ model to be backfilled from Connect-stage signals. Confluence
pageId=208903964 QA#2 captured the gap: path-2 AddKVMHost leaves
these three columns null.

InitPhysicalServerCapacityFlow runs at chain head (positions
2-5/10) per ADR-012 fail-loud ordering, before
saveGeneralHostHardwareFacts writes SYSTEM_SERIAL_NUMBER /
SYSTEM_MANUFACTURER / SYSTEM_PRODUCT_NAME at position 7+.
Reading SystemTag there returns null.

Hook the chain tail call-after-add-host-extension instead
(HostAddExtensionPoint.afterAddHost) — fires once tags are
written. Null-only update preserves any user-supplied value from
path-1 CreatePhysicalServer.

KVM only. BM2 / Container chassis have no top-level identity
columns; their backfill ships once the FRU / nodeInfo discovery
adapters land.

PhysicalServerKvmBackfillCase covers tier-3 auto-create,
pre-resolved serverUuid, and idempotent preservation of
user-supplied manufacturer.

Change-Id: Iba0069ef7fcbd5b747750796e1bdedb7960ac6c9
@ExceptionSafe handles uncaught throwables uniformly via AspectJ;
local try-catch in afterAddHost only added noise and would have
silently swallowed real bugs. Plain logic, completion.success()
at the end. PRD §2.5.1 backfill behavior unchanged.

Change-Id: I371208a31119e6781c8125b489746bcd72479557
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Walkthrough

此PR通过数据库schema更新、加密框架依赖隔离、硬件字段精简、API验证增强和测试基础设施改进,重构了物理服务器硬件信息模型。新增KVM主机身份回填机制和电源检测测试注入重构,完善了系统的数据完整性和测试可覆盖性。

Changes

Physical Server Hardware Refactoring

Layer / File(s) Summary
Encryption Framework Dependency Isolation
header/src/main/java/org/zstack/header/convert/EncryptFacade.java, header/src/main/java/org/zstack/header/convert/PasswordConverter.java, core/src/main/java/org/zstack/core/encrypt/EncryptFacadeImpl.java, core/src/main/java/org/zstack/core/convert/SpecialDataConverter.java
EncryptFacade 接口和 PasswordConverter 转换器从 core 包迁移到 header 包;新增 header 侧接口定义,包含 isEncryptionDisabled() 方法用于全局加密开关判定;更新 core 侧实现和所有依赖项导入。
Hardware Schema and Model Simplification
conf/db/upgrade/V5.5.18__schema.sql, header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java, header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
从数据库表、持久化VO和扩展点接口中删除硬件计数字段(memoryModuleCountdiskCountnicCountgpuCount)及 healthStatusdiscoverSource;保留CPU架构、总内存、总磁盘字节等总量字段。
Hardware DTO and Service Layer
plugin/physicalServer/src/main/java/org/zstack/server/hardware/UnifiedHardwareInfo.java, plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
删除 UnifiedHardwareInfo DTO 中计数类字段的声明和访问器;调整 PhysicalServerHardwareServicemergeNonNull()applyNonNull() 方法,移除计数字段的合并和持久化逻辑;删除 discoverSource 参数。
Hardware Field Simplification Unit Tests
plugin/physicalServer/src/test/java/org/zstack/server/hardware/UnifiedHardwareInfoMergeTest.java
更新 UnifiedHardwareInfoMergeTest 的测试断言,反映硬件字段删除后的合并、应用和投影逻辑变化;调整验证字段集合以匹配新的DTO模型。
OOB Password Encryption Integration
header/src/main/java/org/zstack/header/server/PhysicalServerAO.java, test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy
PhysicalServerAO.oobPassword 字段添加 @Convert(converter = PasswordConverter.class) 注解启用JPA加密转换;新增 PhysicalServerOobPasswordEncryptCase 集成测试验证加密元数据注册、加密状态转换和读写一致性。
API Validation and Constraint Enforcement
plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java, test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerInterceptorErrorsCase.groovy
在拦截器中添加三组验证:角色重复绑定检查、同zone内序列号唯一性检查、GATEWAY_PXE网络DHCP字段完整性检查;新增 PhysicalServerInterceptorErrorsCase 集成测试覆盖各验证场景和错误消息准确性。
KVM Host Identity Backfill Extension
plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerKvmIdentityBackfillExtension.java, test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerKvmBackfillCase.groovy
实现 PhysicalServerKvmIdentityBackfillExtension 主机添加后钩子,检测KVM主机并从系统标签回填 serialNumbermanufacturermodel 字段(仅当为null时);新增 PhysicalServerKvmBackfillCase 测试验证自动创建、预建和用户设置字段保护场景。
Power Status Detection Refactoring
plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerPowerTracker.java, plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerManagerImpl.java, plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerScanner.java
将IPMI电源状态检测的测试注入点从高层 PhysicalServerPowerStatus 改为低层 ShellResult;提取 runIpmiPowerStatus() 方法集中处理shell执行和注入点;使真实分类和解析逻辑在测试路径可执行。
Test Configuration and Integration Updates
test/src/test/groovy/org/zstack/test/integration/kvm/KvmTest.groovy, test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOpsCase.groovy, test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
更新 KvmTest 的Spring配置,移除 PhysicalServerTestProviders.xml,新增 LongJobManager.xmlHostAllocateExtension.xmlPhysicalServerManager.xml;重构 PhysicalServerOpsCase 以使用 ShellResult 驱动电源状态测试;调整导入路径以指向新的加密框架包。
Password Converter Import Updates
plugin/ceph/src/main/java/org/zstack/storage/ceph/CephMonAO.java, plugin/kvm/src/main/java/org/zstack/kvm/KVMHostVO.java, plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
更新三个VO类中 PasswordConverter 的导入路径,从 org.zstack.core.convert 切换到 org.zstack.header.convert,保持字段注解不变。
Documentation and Follow-ups
docs/brainstorms/, docs/review/, docs/todolist.md
新增多份文档:2026-05-12-followups.md 记录P0-P2待办事项;v5518-ut-on-bypass-audit.md 审计UNIT_TEST_ON覆盖缺口;assertion-changelog.mdcase-fix-log.md 记录测试用例修复台账;next-session.mdtodolist.md 维护任务清单。

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

兔子翻过了硬件堆,

加密框架换个家,

计数字段都飘零,

验证守卫更周密,🔐

测试注入真可控。✨


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 标题包含JIRA编号但未遵循要求的格式规范:缺少type[scope]前缀和冒号分隔,未采用'[scope]: '格式。 调整标题格式为'[scope]: ',如'fix[physical-server]: unified-hardware QA bugs 85182/85184/85190/85350',确保总长度不超过72字符。
Docstring Coverage ⚠️ Warning Docstring coverage is 13.85% 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描述清晰完整,涵盖修复范围、提交汇总、集成测试结果、E2E验证与相关JIRA编号,与变更集的核心改动(密码加密框架迁移、OOB密码字段注解、API拦截器预检查)高度相关。
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/jin.ma/cordon-container@@2

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

🤖 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 `@header/src/main/java/org/zstack/header/core/encrypt/PasswordConverter.java`:
- Around line 39-40: In PasswordConverter, do not treat encryptFacade == null as
a permissive passthrough; update both convertToEntityAttribute and
convertToDatabaseColumn so they only return the raw attribute/value when
encryptFacade.isEncryptionDisabled() is true, and if encryptFacade is null then
throw an explicit runtime exception (e.g., IllegalStateException) or otherwise
fail-fast (with a clear error message) to block writing cleartext passwords;
reference the class PasswordConverter and its methods
convertToEntityAttribute/convertToDatabaseColumn and the
encryptFacade/isEncryptionDisabled symbols when making this change.

In
`@plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`:
- Around line 84-95: Trim message string fields before performing
required/uniqueness checks: in PhysicalServerApiInterceptor, obtain a trimmed
value from msg.getSerialNumber() (e.g., call trim() if not null) and use that
trimmed value for the empty-check and the Q query (zoneUuid/serialNumber
uniqueness) and in the ApiMessageInterceptionException message; apply the same
trim-before-validate change to the other block referenced (the similar
validation around msg.getSerialNumber() at the later check). Ensure you never
rely on raw msg.getSerialNumber() for isEmpty() or the Q.New(...) eq(...)
comparison—always use the normalized trimmed string.

In
`@test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerKvmBackfillCase.groovy`:
- Around line 20-33: Replace all Chinese text in the test file’s comments and
assertion messages with clear, correct English; specifically update the
class-level comment block describing QA gap and data flow and any
assertion/expectation messages within the test methods
testTier3AutoCreatedPsBackfilled, testPreResolvedPsBackfilled, and
testUserSuppliedFieldNotOverwritten so they are fully in English with no
spelling errors, preserving the original meaning (e.g., describe backfill
behavior, fields serialNumber/manufacturer/model, and that user-supplied
manufacturer is not overwritten).
🪄 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: d4203345-4101-4f7d-8010-27dd942de2b6

📥 Commits

Reviewing files that changed from the base of the PR and between 658e302 and a3bdf52.

⛔ Files ignored due to path filters (3)
  • conf/springConfigXml/PhysicalServerManager.xml is excluded by !**/*.xml
  • conf/springConfigXml/encrypt.xml is excluded by !**/*.xml
  • test/src/test/resources/springConfigXml/PhysicalServerTestProviders.xml is excluded by !**/*.xml
📒 Files selected for processing (28)
  • conf/db/upgrade/V5.5.18__schema.sql
  • core/src/main/java/org/zstack/core/convert/SpecialDataConverter.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacadeImpl.java
  • header/src/main/java/org/zstack/header/core/encrypt/EncryptFacade.java
  • header/src/main/java/org/zstack/header/core/encrypt/PasswordConverter.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerAO.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/CephMonAO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostVO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/server/KvmHardwareDiscoveryAdapter.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerKvmIdentityBackfillExtension.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerManagerImpl.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerPowerTracker.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerScanner.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/UnifiedHardwareInfo.java
  • plugin/physicalServer/src/test/java/org/zstack/server/hardware/UnifiedHardwareInfoMergeTest.java
  • plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/KvmTest.groovy
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerInterceptorErrorsCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerKvmBackfillCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOpsCase.groovy
  • test/src/test/java/org/zstack/test/TestGatewayPxeProvisionProvider.java
💤 Files with no reviewable changes (8)
  • test/src/test/java/org/zstack/test/TestGatewayPxeProvisionProvider.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • conf/db/upgrade/V5.5.18__schema.sql
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/KvmTest.groovy
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/UnifiedHardwareInfo.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java

Comment on lines +39 to 40
if (encryptFacade == null || encryptFacade.isEncryptionDisabled()) {
return attribute;
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

encryptFacade == null 不应降级为明文透传

Line 39 和 Line 50 把 encryptFacade == null 视为“直接返回原值”,会在注入异常/初始化异常时静默写入明文密码,属于 fail-open。建议仅在 isEncryptionDisabled() 时透传,encryptFacade == null 应显式失败(或至少告警并阻断写入)。

🔧 建议修复
 `@Override`
 public String convertToDatabaseColumn(String attribute) {
-    if (encryptFacade == null || encryptFacade.isEncryptionDisabled()) {
+    if (encryptFacade == null) {
+        throw new IllegalStateException("EncryptFacade is not initialized");
+    }
+    if (encryptFacade.isEncryptionDisabled()) {
         return attribute;
     }
     if (StringUtils.isEmpty(attribute)) {
         return attribute;
     }
     return encryptFacade.encrypt(attribute);
 }

 `@Override`
 public String convertToEntityAttribute(String dbData) {
-    if (encryptFacade == null || encryptFacade.isEncryptionDisabled()) {
+    if (encryptFacade == null) {
+        throw new IllegalStateException("EncryptFacade is not initialized");
+    }
+    if (encryptFacade.isEncryptionDisabled()) {
         return dbData;
     }

     if (StringUtils.isEmpty(dbData)) {
         return dbData ;
     }

     return encryptFacade.decrypt(dbData);
 }

Also applies to: 50-51

🤖 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 `@header/src/main/java/org/zstack/header/core/encrypt/PasswordConverter.java`
around lines 39 - 40, In PasswordConverter, do not treat encryptFacade == null
as a permissive passthrough; update both convertToEntityAttribute and
convertToDatabaseColumn so they only return the raw attribute/value when
encryptFacade.isEncryptionDisabled() is true, and if encryptFacade is null then
throw an explicit runtime exception (e.g., IllegalStateException) or otherwise
fail-fast (with a clear error message) to block writing cleartext passwords;
reference the class PasswordConverter and its methods
convertToEntityAttribute/convertToDatabaseColumn and the
encryptFacade/isEncryptionDisabled symbols when making this change.

Comment on lines +84 to +95
if (msg.getSerialNumber() != null && !msg.getSerialNumber().isEmpty() && msg.getZoneUuid() != null) {
boolean dup = Q.New(PhysicalServerVO.class)
.eq(PhysicalServerAO_.zoneUuid, msg.getZoneUuid())
.eq(PhysicalServerAO_.serialNumber, msg.getSerialNumber())
.isExists();
if (dup) {
throw new ApiMessageInterceptionException(argerr(
"ORG_ZSTACK_PHYSICAL_SERVER_SERIAL_NUMBER_DUPLICATE",
"PhysicalServer with serialNumber[%s] already exists in Zone[uuid:%s]; serialNumber must be unique per zone",
msg.getSerialNumber(), msg.getZoneUuid()));
}
}
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

先对消息字段做 trim,再做必填/唯一性校验。

这里直接用 isEmpty(),会把仅空白字符当作有效值,导致 DHCP 必填校验可被空格绕过,serialNumber 去重也会基于未规整值执行。

🔧 建议修改
+    private static boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
     private void validate(APICreatePhysicalServerMsg msg) {
@@
-        if (msg.getSerialNumber() != null && !msg.getSerialNumber().isEmpty() && msg.getZoneUuid() != null) {
+        String serialNumber = msg.getSerialNumber() == null ? null : msg.getSerialNumber().trim();
+        if (!isBlank(serialNumber) && msg.getZoneUuid() != null) {
             boolean dup = Q.New(PhysicalServerVO.class)
                 .eq(PhysicalServerAO_.zoneUuid, msg.getZoneUuid())
-                .eq(PhysicalServerAO_.serialNumber, msg.getSerialNumber())
+                .eq(PhysicalServerAO_.serialNumber, serialNumber)
                 .isExists();
             if (dup) {
                 throw new ApiMessageInterceptionException(argerr(
                     "ORG_ZSTACK_PHYSICAL_SERVER_SERIAL_NUMBER_DUPLICATE",
                     "PhysicalServer with serialNumber[%s] already exists in Zone[uuid:%s]; serialNumber must be unique per zone",
-                    msg.getSerialNumber(), msg.getZoneUuid()));
+                    serialNumber, msg.getZoneUuid()));
             }
         }
     }
@@
-        if ("GATEWAY_PXE".equals(msg.getType())) {
+        String type = msg.getType() == null ? null : msg.getType().trim();
+        if ("GATEWAY_PXE".equals(type)) {
             java.util.List<String> missing = new java.util.ArrayList<>();
-            if (msg.getDhcpInterface() == null || msg.getDhcpInterface().isEmpty()) {
+            if (isBlank(msg.getDhcpInterface())) {
                 missing.add("dhcpInterface");
             }
-            if (msg.getDhcpRangeStartIp() == null || msg.getDhcpRangeStartIp().isEmpty()) {
+            if (isBlank(msg.getDhcpRangeStartIp())) {
                 missing.add("dhcpRangeStartIp");
             }
-            if (msg.getDhcpRangeEndIp() == null || msg.getDhcpRangeEndIp().isEmpty()) {
+            if (isBlank(msg.getDhcpRangeEndIp())) {
                 missing.add("dhcpRangeEndIp");
             }
-            if (msg.getDhcpRangeNetmask() == null || msg.getDhcpRangeNetmask().isEmpty()) {
+            if (isBlank(msg.getDhcpRangeNetmask())) {
                 missing.add("dhcpRangeNetmask");
             }

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

Also applies to: 122-135

🤖 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/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`
around lines 84 - 95, Trim message string fields before performing
required/uniqueness checks: in PhysicalServerApiInterceptor, obtain a trimmed
value from msg.getSerialNumber() (e.g., call trim() if not null) and use that
trimmed value for the empty-check and the Q query (zoneUuid/serialNumber
uniqueness) and in the ApiMessageInterceptionException message; apply the same
trim-before-validate change to the other block referenced (the similar
validation around msg.getSerialNumber() at the later check). Ensure you never
rely on raw msg.getSerialNumber() for isEmpty() or the Q.New(...) eq(...)
comparison—always use the normalized trimmed string.

Comment on lines +20 to +33
* QA gap (Confluence pageId=208903964 #2) — 真实物理机 path-2 添加后 PhysicalServerVO
* 的 serialNumber / manufacturer / model 一直为 null,违反 PRD §2.5.1 明文要求:
* "这些字段由 InitPhysicalServerCapacityFlow(或 AutoAssociateFlow)从 Connect 阶段
* 已获取的基础信息回填 —— 不依赖 HardwareDiscoveryQueue"。
*
* 数据通路:KVM agent (simulator override) -> HostFactResponse.systemSerialNumber/
* SystemManufacturer/SystemProductName -> saveGeneralHostHardwareFacts() 写 HostSystemTag
* -> InitPhysicalServerCapacityFlow 读 tag null-only 回填 PS.
*
* 覆盖:
* testTier3AutoCreatedPsBackfilled — AutoAssociate 新建 PS(三字段全 null)→ 全部回填
* testPreResolvedPsBackfilled — caller 提供 serverUuid,预建 PS(三字段全 null)→ 全部回填
* testUserSuppliedFieldNotOverwritten — 预建 PS 手填 manufacturer → backfill 不动它
*/
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

将中文注释和断言文案统一改为英文。

当前这几处包含中文(注释/断言消息),与仓库的语言规范冲突,建议统一替换为无拼写错误的英文。

✏️ 示例
- "QA-2 失败: PS.serialNumber expected=${FAKE_SN}, got=${ps.serialNumber}"
+ "QA-2 failed: PS.serialNumber expected=${FAKE_SN}, got=${ps.serialNumber}"

As per coding guidelines **/*.*: 代码里不应当有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写。

Also applies to: 103-107, 152-156, 196-197

🤖 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/server/PhysicalServerKvmBackfillCase.groovy`
around lines 20 - 33, Replace all Chinese text in the test file’s comments and
assertion messages with clear, correct English; specifically update the
class-level comment block describing QA gap and data flow and any
assertion/expectation messages within the test methods
testTier3AutoCreatedPsBackfilled, testPreResolvedPsBackfilled, and
testUserSuppliedFieldNotOverwritten so they are fully in English with no
spelling errors, preserving the original meaning (e.g., describe backfill
behavior, fields serialNumber/manufacturer/model, and that user-supplied
manufacturer is not overwritten).

@MatheMatrix MatheMatrix force-pushed the sync/jin.ma/cordon-container@@2 branch from a3bdf52 to 94b8d12 Compare May 21, 2026 05:49
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: 4

🤖 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 `@docs/brainstorms/2026-05-12-followups.md`:
- Around line 27-33: The markdown table beginning with "3 处 prod 早返:" is missing
blank lines immediately before and after it, causing MD058; fix by inserting a
blank line above the table header and a blank line after the table end (the
closing |---|---|---|---|---| row block) so the table is separated from
surrounding text; update the same section in
docs/brainstorms/2026-05-12-followups.md where the table appears to ensure
there's an empty line before the line "3 处 prod 早返:" and another empty line
after the final table row.

In `@docs/brainstorms/archives/2026-05-11-case-fix-log.md`:
- Around line 74-77: The fenced code block that starts with
"org.codehaus.groovy.runtime.powerassert.PowerAssertionError:" is missing a
language tag and triggers MD040; update that fenced block to include a language
identifier (e.g., change ``` to ```text) so the block becomes ```text ... ```;
locate the block containing "assert 4 == createConfigCmds.size()" and add the
language marker to fix the lint error.
- Around line 3-10: Remove the empty blank line inside the blockquote that
starts with "**2026-05-12 人工复核结论(最权威,新 agent 扫描请以此为准)**:" (the quoted paragraph
containing "BM2NicCase 4→5 + line 474 revert ✅ 已人工验证可信"); simply delete the
stray blank line so there are no blank lines inside the blockquote (resolving
MD028 / no-blanks-blockquote).

In
`@test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy`:
- Around line 20-22: 注释中的包路径与实际代码迁移不一致(注释写了 header.core.encrypt,但类实际在
org.zstack.header.encrypt),请把注释中涉及 PasswordConverter 和 EncryptFacade 的包路径从
header.core.encrypt 修改为 org.zstack.header.encrypt,并同时检查并更新提到的 PhysicalServerAO
的描述以保持一致;确保注释中所有包名引用与代码中类型所在包(org.zstack.header.encrypt)完全匹配。
🪄 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: d7490b22-3412-46d8-84ac-2573604d1981

📥 Commits

Reviewing files that changed from the base of the PR and between a3bdf52 and 94b8d12.

⛔ Files ignored due to path filters (1)
  • conf/springConfigXml/encrypt.xml is excluded by !**/*.xml
📒 Files selected for processing (25)
  • conf/db/upgrade/V5.5.18__schema.sql
  • core/src/main/java/org/zstack/core/convert/SpecialDataConverter.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacadeImpl.java
  • docs/brainstorms/2026-05-12-followups.md
  • docs/brainstorms/archives/2026-05-11-assertion-changelog.md
  • docs/brainstorms/archives/2026-05-11-case-fix-log.md
  • docs/brainstorms/next-session.md
  • docs/review/v5518-ut-on-bypass-audit.md
  • docs/todolist.md
  • header/src/main/java/org/zstack/header/encrypt/EncryptFacade.java
  • header/src/main/java/org/zstack/header/encrypt/PasswordConverter.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerAO.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/CephMonAO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostVO.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/UnifiedHardwareInfo.java
  • plugin/physicalServer/src/test/java/org/zstack/server/hardware/UnifiedHardwareInfoMergeTest.java
  • plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerInterceptorErrorsCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy
💤 Files with no reviewable changes (5)
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • conf/db/upgrade/V5.5.18__schema.sql
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java
✅ Files skipped from review due to trivial changes (5)
  • docs/brainstorms/next-session.md
  • plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
  • docs/brainstorms/archives/2026-05-11-assertion-changelog.md
  • header/src/main/java/org/zstack/header/server/PhysicalServerAO.java

Comment thread docs/brainstorms/2026-05-12-followups.md
Comment on lines +3 to +10
> **作用**:复盘 2026-05-10 / 2026-05-11 两天对本 MR 所有 case 的改动,留作 review +
> 审计资料。**重点标注哪些 case 的 assertion 数值/逻辑被改了,但没在代码里留 inline
> 注释或 Jira/MR 引用**——这是后续防止「修 case 只压症状不留痕迹」的关键线索。
>
> 本文档**只读** worktree、**不动** tracked 文件。

> **2026-05-12 人工复核结论(最权威,新 agent 扫描请以此为准)**:
> 1. **BM2NicCase 4→5 + line 474 revert ✅ 已人工验证可信**:MR prod 改动让 retryInSecs 真的多发 1 次 CREATE/PREPARE,5 是 prod 真值;line 474 由 premium `d453e70ba7 <fix>[premium]: PSC cleanup + BM2NicCase revert` 还原原 `assert 0L == ...ClusterRefVO.count()`,对应 V5.5.18 schema 删 orphan FK DROP 已 land。§2.1 / §5 BM2 nic 违规清单与 §7 Action #1-#2-#3 **已 closed,无需再追**。
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

移除 blockquote 内空白行,避免 MD028 告警。

Line 8 在引用块中出现空白行,markdownlint 会报 no-blanks-blockquote

建议修改
 > 注释或 Jira/MR 引用**——这是后续防止「修 case 只压症状不留痕迹」的关键线索。
->
 > 本文档**只读** worktree、**不动** tracked 文件。
📝 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
> **作用**:复盘 2026-05-10 / 2026-05-11 两天对本 MR 所有 case 的改动,留作 review +
> 审计资料。**重点标注哪些 case 的 assertion 数值/逻辑被改了,但没在代码里留 inline
> 注释或 Jira/MR 引用**——这是后续防止「修 case 只压症状不留痕迹」的关键线索。
>
> 本文档**只读** worktree、**不动** tracked 文件。
> **2026-05-12 人工复核结论(最权威,新 agent 扫描请以此为准)**
> 1. **BM2NicCase 4→5 + line 474 revert ✅ 已人工验证可信**:MR prod 改动让 retryInSecs 真的多发 1 次 CREATE/PREPARE,5 是 prod 真值;line 474 由 premium `d453e70ba7 <fix>[premium]: PSC cleanup + BM2NicCase revert` 还原原 `assert 0L == ...ClusterRefVO.count()`,对应 V5.5.18 schema 删 orphan FK DROP 已 land。§2.1 / §5 BM2 nic 违规清单与 §7 Action #1-#2-#3 **已 closed,无需再追**
> **作用**:复盘 2026-05-10 / 2026-05-11 两天对本 MR 所有 case 的改动,留作 review +
> 审计资料。**重点标注哪些 case 的 assertion 数值/逻辑被改了,但没在代码里留 inline
> 注释或 Jira/MR 引用**——这是后续防止「修 case 只压症状不留痕迹」的关键线索。
> 本文档**只读** worktree、**不动** tracked 文件。
> **2026-05-12 人工复核结论(最权威,新 agent 扫描请以此为准)**
> 1. **BM2NicCase 4→5 + line 474 revert ✅ 已人工验证可信**:MR prod 改动让 retryInSecs 真的多发 1 次 CREATE/PREPARE,5 是 prod 真值;line 474 由 premium `d453e70ba7 <fix>[premium]: PSC cleanup + BM2NicCase revert` 还原原 `assert 0L == ...ClusterRefVO.count()`,对应 V5.5.18 schema 删 orphan FK DROP 已 land。§2.1 / §5 BM2 nic 违规清单与 §7 Action `#1-`#2-#3 **已 closed,无需再追**
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 8-8: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🤖 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 `@docs/brainstorms/archives/2026-05-11-case-fix-log.md` around lines 3 - 10,
Remove the empty blank line inside the blockquote that starts with "**2026-05-12
人工复核结论(最权威,新 agent 扫描请以此为准)**:" (the quoted paragraph containing "BM2NicCase 4→5
+ line 474 revert ✅ 已人工验证可信"); simply delete the stray blank line so there are
no blank lines inside the blockquote (resolving MD028 / no-blanks-blockquote).

Comment on lines +74 to +77
```
org.codehaus.groovy.runtime.powerassert.PowerAssertionError:
assert 4 == createConfigCmds.size()
```
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

为代码块补充语言标识,避免 MD040。

Line 74 的 fenced code block 缺少语言类型。

建议修改
-```
+```text
 org.codehaus.groovy.runtime.powerassert.PowerAssertionError:
   assert 4 == createConfigCmds.size()
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 74-74: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @docs/brainstorms/archives/2026-05-11-case-fix-log.md around lines 74 - 77,
The fenced code block that starts with
"org.codehaus.groovy.runtime.powerassert.PowerAssertionError:" is missing a
language tag and triggers MD040; update that fenced block to include a language
identifier (e.g., change totext) so the block becomes text ... ;
locate the block containing "assert 4 == createConfigCmds.size()" and add the
language marker to fix the lint error.


</details>

<!-- fingerprinting:phantom:triton:hawk -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +20 to +22
* <p>The fix relocates {@code PasswordConverter} and {@code EncryptFacade} to
* {@code header.core.encrypt} so {@link org.zstack.header.server.PhysicalServerAO}
* can wire {@code @Convert(PasswordConverter.class)} directly — same mechanism
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

注释中的包路径与实际迁移结果不一致

Line 21 写的是 header.core.encrypt,但当前迁移后的类型位于 org.zstack.header.encrypt。建议同步修正,避免后续排障按注释误定位。

🔧 建议修复
- * {`@code` header.core.encrypt} so {`@link` org.zstack.header.server.PhysicalServerAO}
+ * {`@code` header.encrypt} so {`@link` org.zstack.header.server.PhysicalServerAO}
📝 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
* <p>The fix relocates {@code PasswordConverter} and {@code EncryptFacade} to
* {@code header.core.encrypt} so {@link org.zstack.header.server.PhysicalServerAO}
* can wire {@code @Convert(PasswordConverter.class)} directly — same mechanism
* <p>The fix relocates {`@code` PasswordConverter} and {`@code` EncryptFacade} to
* {`@code` header.encrypt} so {`@link` org.zstack.header.server.PhysicalServerAO}
* can wire {`@code` `@Convert`(PasswordConverter.class)} directly — same mechanism
🤖 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/server/PhysicalServerOobPasswordEncryptCase.groovy`
around lines 20 - 22, 注释中的包路径与实际代码迁移不一致(注释写了 header.core.encrypt,但类实际在
org.zstack.header.encrypt),请把注释中涉及 PasswordConverter 和 EncryptFacade 的包路径从
header.core.encrypt 修改为 org.zstack.header.encrypt,并同时检查并更新提到的 PhysicalServerAO
的描述以保持一致;确保注释中所有包名引用与代码中类型所在包(org.zstack.header.encrypt)完全匹配。

MaJin1996 added 6 commits May 21, 2026 14:13
Per QA review (Confluence pageId=208903964): cpuSockets, cpuCores,
memoryModuleCount, diskCount, nicCount, gpuCount on
PhysicalServerHardwareInfoVO have no consistent owner across the
three discovery adapters and were not actually surfaced to
operators. Dropping them removes a class of stale-data foot-guns.

Touched layers, all in lockstep:
- V5.5.18 Flyway schema (still pre-release per ADR-007).
- PhysicalServerHardwareInfoVO entity fields + accessors.
- HardwareInfoCarrier SPI setters (interface contract).
- UnifiedHardwareInfo DTO fields + accessors.
- PhysicalServerHardwareService getHardware / mergeNonNull /
  applyNonNull blocks.
- KvmHardwareDiscoveryAdapter (HostCapacityVO write-throughs).
- Bm2IpmiFruDiscoveryAdapter (offering CPU count + chassis Nic /
  Disk / Gpu COUNT() probes — kept totalDiskBytes sum).
- ContainerNodeInfoDiscoveryAdapter (K8s capacity.cpu / 1000
  cores write-through).

Keeps cpuModel / cpuArchitecture / totalMemoryBytes /
totalDiskBytes / healthStatus + system identity fields, which the
adapters do populate reliably. HostCapacityVO.cpuSockets stays —
that's the allocator side, not the discovery summary.

Change-Id: I5285dbb857340ac7aa71c11b8c50674fa7365099
Fallout from ad45ca4 removing 6 count fields from
UnifiedHardwareInfo / PhysicalServerHardwareInfoVO. Adapt the
merge tests to use totalMemoryBytes as the numeric-zero canary
and drop deleted setters/getters. 9 cases pass.

Change-Id: Ib83f0f565cde561f4729fbdc8acba274a33b8a7d
PhysicalServerVO lives in header which cannot depend on
core.convert.PasswordConverter, so the standard JPA
AttributeConverter pattern used by KVMHostVO is unavailable.
The @EncryptColumn marker on oobPassword only registers the
column for integrity checking — its javadoc explicitly notes
the field is not encrypted.

Funnel every write site through
PhysicalServerOobPasswordCrypto.encryptForStorage and every
plaintext read site through decryptFromStorage so the DB
column never holds plaintext:
- CreatePhysicalServer / UpdatePhysicalServer / scanner.
- probePowerStatus tempfile + IpmiPowerExecutor caller.
- ProvisionService forward to PhysicalServerProvisionTarget
  and AttachPhysicalServerRole forward to
  CreateRoleEntityContext.

IT verifies raw column != plaintext, decrypt round-trips,
update path re-encrypts, and disabled-encryption mode is a
pass-through (matches PasswordConverter contract).

Resolves: ZSTAC-85182

Change-Id: If506f4bcfefc0957173ba5990e396f098787114d
Relocate PasswordConverter and EncryptFacade interface
from core to header.core.encrypt so header-resident
VOs (notably PhysicalServerAO.oobPassword for
ZSTAC-85182) can apply @convert(PasswordConverter.class)
directly, mirroring the KVMHostVO.password mechanism.

EncryptFacade picks up isEncryptionDisabled() so the
converter no longer needs to import EncryptGlobalConfig.
EncryptFacadeImpl in core overrides the new method.

Mechanical FQN updates across existing import sites
(KVMHostVO, CephMonAO, SftpBackupStorageVO,
HostPasswordEncryptCase, SpecialDataConverter,
encrypt.xml bean class).

Resolves: ZSTAC-85182

Change-Id: Ied6ba0daecabda732e68335a8f333e7e40ca4789
Now that PasswordConverter lives in header, annotate
PhysicalServerAO.oobPassword with @convert(PasswordConverter.class).
Hibernate handles encrypt-on-write and decrypt-on-read
transparently — same mechanism KVMHostVO.password uses.

Removes the application-layer PhysicalServerOobPasswordCrypto
helper introduced as a stopgap: managers and read sites
now use the plain getter/setter, simpler and on par with
the rest of the platform.

IT PhysicalServerOobPasswordEncryptCase now mirrors
HostPasswordEncryptCase: asserts the @convert field is
registered in EncryptEntityMetadataVO, that the toggle
flips to Encrypted, and that the converter is a pass-
through when encryption is globally disabled.

Resolves: ZSTAC-85182

Change-Id: I083434f27cacc9ab4b03a3b1f2ae13ae9b5aa0b5
PhysicalServerApiInterceptor now surfaces named errors
instead of bubbling SYS.1000 constraint violations or
SYS.1006 with just a uuid as details.

- ZSTAC-85184 CreatePhysicalServer: pre-check
  zone+serialNumber uniqueness; explicit message names
  the offending serialNumber and Zone.
- ZSTAC-85190 AttachPhysicalServerRole: pre-check
  (serverUuid, roleType); explicit message names the
  conflicting role and suggests detach-first.
- ZSTAC-85350 CreateProvisionNetwork GATEWAY_PXE:
  reject when DHCP wiring (dhcpInterface +
  dhcpRangeStartIp/EndIp/Netmask) is missing; lists
  the missing fields in one consolidated error.

All three errors are emitted via 3-arg argerr so
globalErrorCode + formatted details land in the
response payload.

IT PhysicalServerInterceptorErrorsCase exercises all
three error paths and asserts the offending field
appears in the message.

Resolves: ZSTAC-85184

Change-Id: Ia5779b95fad27c3e2c826014d0116557089eff5a
@MatheMatrix MatheMatrix force-pushed the sync/jin.ma/cordon-container@@2 branch from 94b8d12 to e424b7e Compare May 21, 2026 06:41
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: 1

♻️ Duplicate comments (4)
test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy (1)

20-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

注释中的包路径与实际迁移结果不一致

这里写成了 header.core.encrypt,但当前 PasswordConverterEncryptFacade 位于 org.zstack.header.convert。建议同步修正注释,避免误导定位。

建议修复
- * <p>The fix relocates {`@code` PasswordConverter} and {`@code` EncryptFacade} to
- * {`@code` header.core.encrypt} so {`@link` org.zstack.header.server.PhysicalServerAO}
+ * <p>The fix relocates {`@code` PasswordConverter} and {`@code` EncryptFacade} to
+ * {`@code` org.zstack.header.convert} so {`@link` org.zstack.header.server.PhysicalServerAO}
🤖 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/server/PhysicalServerOobPasswordEncryptCase.groovy`
around lines 20 - 22, 注释中写的包路径 header.core.encrypt 与实际类所在包不一致;把注释中提到的
PasswordConverter 和 EncryptFacade 的包路径改为实际迁移后的包
org.zstack.header.convert(或直接移除错误的包名),并同时确认注释中提到的 PhysicalServerAO 的
`@Convert`(PasswordConverter.class) 描述与当前代码匹配,以避免误导定位。
header/src/main/java/org/zstack/header/convert/PasswordConverter.java (1)

39-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

encryptFacade == null 不应走明文透传路径

当前逻辑在注入异常或初始化异常时会静默降级为明文处理,存在 fail-open 风险。建议 encryptFacade == null 时直接 fail-fast,仅在 isEncryptionDisabled() 为 true 时透传。

建议修复
 `@Override`
 public String convertToDatabaseColumn(String attribute) {
-    if (encryptFacade == null || encryptFacade.isEncryptionDisabled()) {
+    if (encryptFacade == null) {
+        throw new IllegalStateException("EncryptFacade is not initialized");
+    }
+    if (encryptFacade.isEncryptionDisabled()) {
         return attribute;
     }
     if (StringUtils.isEmpty(attribute)) {
         return attribute;
     }
     return encryptFacade.encrypt(attribute);
 }

 `@Override`
 public String convertToEntityAttribute(String dbData) {
-    if (encryptFacade == null || encryptFacade.isEncryptionDisabled()) {
+    if (encryptFacade == null) {
+        throw new IllegalStateException("EncryptFacade is not initialized");
+    }
+    if (encryptFacade.isEncryptionDisabled()) {
         return dbData;
     }

     if (StringUtils.isEmpty(dbData)) {
         return dbData ;
     }

     return encryptFacade.decrypt(dbData);
 }

Also applies to: 50-51

🤖 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 `@header/src/main/java/org/zstack/header/convert/PasswordConverter.java` around
lines 39 - 40, The current null-check allows encryptFacade == null to fall
through to plaintext; update PasswordConverter so that when encryptFacade ==
null it fails fast (throw an IllegalStateException or similar) instead of
returning attribute, and only allow plaintext passthrough when encryptFacade !=
null && encryptFacade.isEncryptionDisabled() is true; apply the same change to
the other similar null/check block (the second occurrence around lines 50-51) so
both code paths use encryptFacade != null &&
encryptFacade.isEncryptionDisabled() for passthrough and explicitly error when
encryptFacade is null.
plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java (2)

84-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

先对 serialNumber 参数执行 trim,再进行必填和唯一性校验。

此问题在之前的审查中已被指出但尚未修复。直接使用 msg.getSerialNumber() 进行 isEmpty() 判断和数据库查询会导致:

  1. 仅包含空白字符的输入被当作有效值
  2. 唯一性校验基于未规整的值执行,可能产生重复记录
🔧 建议修改
 private void validate(APICreatePhysicalServerMsg msg) {
     if (!NetworkUtils.isIpv4Address(msg.getManagementIp())) {
         throw new ApiMessageInterceptionException(argerr("invalid managementIp[%s]", msg.getManagementIp()));
     }

     // Validate poolUuid belongs to same zone
     if (msg.getPoolUuid() != null && msg.getZoneUuid() != null) {
         ServerPoolVO pool = dbf.findByUuid(msg.getPoolUuid(), ServerPoolVO.class);
         if (pool != null && !pool.getZoneUuid().equals(msg.getZoneUuid())) {
             throw new ApiMessageInterceptionException(argerr(
                 "ServerPool[uuid:%s] belongs to Zone[uuid:%s], but PhysicalServer specifies Zone[uuid:%s]",
                 msg.getPoolUuid(), pool.getZoneUuid(), msg.getZoneUuid()
             ));
         }
     }

     // ZSTAC-85184: pre-check serialNumber uniqueness within zone so callers see a
     // readable error instead of the SYS.1000 ConstraintViolationException bubbling
     // from the DB. The DB unique key remains as a belt-and-braces guard for races.
+    String serialNumber = msg.getSerialNumber() == null ? null : msg.getSerialNumber().trim();
-    if (msg.getSerialNumber() != null && !msg.getSerialNumber().isEmpty() && msg.getZoneUuid() != null) {
+    if (serialNumber != null && !serialNumber.isEmpty() && msg.getZoneUuid() != null) {
         boolean dup = Q.New(PhysicalServerVO.class)
             .eq(PhysicalServerAO_.zoneUuid, msg.getZoneUuid())
-            .eq(PhysicalServerAO_.serialNumber, msg.getSerialNumber())
+            .eq(PhysicalServerAO_.serialNumber, serialNumber)
             .isExists();
         if (dup) {
             throw new ApiMessageInterceptionException(argerr(
                 "ORG_ZSTACK_PHYSICAL_SERVER_SERIAL_NUMBER_DUPLICATE",
                 "PhysicalServer with serialNumber[%s] already exists in Zone[uuid:%s]; serialNumber must be unique per zone",
-                msg.getSerialNumber(), msg.getZoneUuid()));
+                serialNumber, msg.getZoneUuid()));
         }
     }
 }

As per coding guidelines **/*Interceptor.java: 注意检查来自 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/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`
around lines 84 - 95, Trim the serialNumber from the Message before doing
required/uniqueness checks in PhysicalServerApiInterceptor: retrieve
msg.getSerialNumber(), call trim() into a local variable (e.g., trimmedSerial),
then use trimmedSerial (not msg.getSerialNumber()) for the null/empty check and
in the Q.New(PhysicalServerVO.class) .eq(PhysicalServerAO_.serialNumber, ...)
existence query and in the ApiMessageInterceptionException message; ensure you
treat a trimmed empty string as missing and enforce uniqueness per zone using
the trimmed value.

122-142: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

先对 type 和 DHCP 相关字段执行 trim,再进行类型判断和必填校验。

此问题在之前的审查中已被指出但尚未修复。直接使用 msg.getType()msg.getDhcpInterface() 等进行判断会导致:

  1. type 为 " GATEWAY_PXE " 时无法匹配校验逻辑
  2. DHCP 字段仅包含空白字符时绕过必填校验
  3. 错误消息中可能包含不可见的空白字符
🔧 建议修改
+    private static boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
 private void validate(APICreateProvisionNetworkMsg msg) {
     if (msg.getDhcpRangeStartIp() != null && !NetworkUtils.isIpv4Address(msg.getDhcpRangeStartIp())) {
         throw new ApiMessageInterceptionException(argerr("invalid dhcpRangeStartIp[%s]", msg.getDhcpRangeStartIp()));
     }
     if (msg.getDhcpRangeEndIp() != null && !NetworkUtils.isIpv4Address(msg.getDhcpRangeEndIp())) {
         throw new ApiMessageInterceptionException(argerr("invalid dhcpRangeEndIp[%s]", msg.getDhcpRangeEndIp()));
     }
     if (msg.getDhcpRangeNetmask() != null && !NetworkUtils.isIpv4Address(msg.getDhcpRangeNetmask())) {
         throw new ApiMessageInterceptionException(argerr("invalid dhcpRangeNetmask[%s]", msg.getDhcpRangeNetmask()));
     }
     if (msg.getDhcpRangeGateway() != null && !NetworkUtils.isIpv4Address(msg.getDhcpRangeGateway())) {
         throw new ApiMessageInterceptionException(argerr("invalid dhcpRangeGateway[%s]", msg.getDhcpRangeGateway()));
     }

     // ZSTAC-85350: GATEWAY_PXE requires DHCP wiring to be usable as a PXE network.
     // Pre-fix the API would persist a half-built ProvisionNetwork with Enabled state
     // that would later fail at provisioning time. Surface the missing fields up-front
     // with one consolidated error so the caller knows exactly what to supply.
+    String type = msg.getType() == null ? null : msg.getType().trim();
-    if ("GATEWAY_PXE".equals(msg.getType())) {
+    if ("GATEWAY_PXE".equals(type)) {
         java.util.List<String> missing = new java.util.ArrayList<>();
-        if (msg.getDhcpInterface() == null || msg.getDhcpInterface().isEmpty()) {
+        if (isBlank(msg.getDhcpInterface())) {
             missing.add("dhcpInterface");
         }
-        if (msg.getDhcpRangeStartIp() == null || msg.getDhcpRangeStartIp().isEmpty()) {
+        if (isBlank(msg.getDhcpRangeStartIp())) {
             missing.add("dhcpRangeStartIp");
         }
-        if (msg.getDhcpRangeEndIp() == null || msg.getDhcpRangeEndIp().isEmpty()) {
+        if (isBlank(msg.getDhcpRangeEndIp())) {
             missing.add("dhcpRangeEndIp");
         }
-        if (msg.getDhcpRangeNetmask() == null || msg.getDhcpRangeNetmask().isEmpty()) {
+        if (isBlank(msg.getDhcpRangeNetmask())) {
             missing.add("dhcpRangeNetmask");
         }
         if (!missing.isEmpty()) {
             throw new ApiMessageInterceptionException(argerr(
                 "ORG_ZSTACK_PROVISION_NETWORK_DHCP_MISSING",
                 "ProvisionNetwork[type:GATEWAY_PXE] requires DHCP wiring; missing field(s): %s",
                 String.join(", ", missing)));
         }
     }
 }

As per coding guidelines **/*Interceptor.java: 注意检查来自 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/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`
around lines 122 - 142, In PhysicalServerApiInterceptor, sanitize inputs by
trimming msg.getType() and all DHCP-related fields before using them: obtain
trimmedType = safeTrim(msg.getType()) and
trimmedDhcpInterface/trimmedDhcpRangeStartIp/trimmedDhcpRangeEndIp/trimmedDhcpRangeNetmask
from msg, use trimmedType for the "GATEWAY_PXE" equality check and use the
trimmed DHCP values when checking emptiness and when building the missing list;
throw the same ApiMessageInterceptionException/argerr with String.join on the
trimmed missing names so error messages do not include invisible whitespace.
🧹 Nitpick comments (2)
plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java (2)

194-194: ⚡ Quick win

将辅助方法可见性收紧为 private

Line 194 位于“private helpers”区块,当前包级可见性会扩大类外可调用面,建议收紧封装边界。

💡 建议修改
-    void persistHardwareInfo(String serverUuid, UnifiedHardwareInfo info) {
+    private void persistHardwareInfo(String serverUuid, UnifiedHardwareInfo info) {
🤖 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/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java`
at line 194, The helper method persistHardwareInfo(String serverUuid,
UnifiedHardwareInfo info) currently has package-private visibility but is
declared among private helpers; change its visibility to private to tighten
encapsulation by updating the method signature of persistHardwareInfo to be
private so it cannot be called from outside the class.

77-82: ⚡ Quick win

建议把 mergeNonNull 改为 void 并同步更新注释契约。

Line 77-80 已不再使用返回值,保留 boolean 返回会误导后续维护者(尤其当前注释仍提到 discoverSource 语义)。

💡 建议修改
-    boolean mergeNonNull(UnifiedHardwareInfo target, UnifiedHardwareInfo source) {
+    void mergeNonNull(UnifiedHardwareInfo target, UnifiedHardwareInfo source) {
         if (source == null) {
-            return false;
+            return;
         }
-        boolean changed = false;
         if (target.getManufacturer() == null && source.getManufacturer() != null) {
             target.setManufacturer(source.getManufacturer());
-            changed = true;
         }
@@
         if (target.getTotalMemoryBytes() == null && source.getTotalMemoryBytes() != null) {
             target.setTotalMemoryBytes(source.getTotalMemoryBytes());
-            changed = true;
         }
-        return changed;
     }
🤖 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/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java`
around lines 77 - 82, The method mergeNonNull currently returns a boolean but
its return value is no longer used (calls like mergeNonNull(merged, runExt(...))
ignore the result); change mergeNonNull's signature to return void and update
its implementation accordingly, then update all call sites (e.g. the sequence
with runExt(SOURCE_IPMI_FRU, server), runExt(SOURCE_KVM_AGENT, server),
runExt(SOURCE_K8S_NODEINFO, server) and subsequent
persistHardwareInfo(serverUuid, merged)) to treat it as a void helper; also
update the method's Javadoc/comments (and any mention of discoverSource
semantics) to reflect the new void contract so the behavior and intent are clear
to future maintainers.
🤖 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
`@plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`:
- Around line 48-59: The validate(APIAttachPhysicalServerRoleMsg msg) method
uses msg.getServerUuid() and msg.getRoleType() directly; trim both values (e.g.,
assign String serverUuid = msg.getServerUuid().trim(); String roleType =
msg.getRoleType().trim()) before using them in the
Q.New(PhysicalServerRoleVO.class) query (PhysicalServerRoleVO_.serverUuid,
PhysicalServerRoleVO_.roleType) and when building the
ApiMessageInterceptionException via argerr so the existence check and the error
message use the normalized (trimmed) values.

---

Duplicate comments:
In `@header/src/main/java/org/zstack/header/convert/PasswordConverter.java`:
- Around line 39-40: The current null-check allows encryptFacade == null to fall
through to plaintext; update PasswordConverter so that when encryptFacade ==
null it fails fast (throw an IllegalStateException or similar) instead of
returning attribute, and only allow plaintext passthrough when encryptFacade !=
null && encryptFacade.isEncryptionDisabled() is true; apply the same change to
the other similar null/check block (the second occurrence around lines 50-51) so
both code paths use encryptFacade != null &&
encryptFacade.isEncryptionDisabled() for passthrough and explicitly error when
encryptFacade is null.

In
`@plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`:
- Around line 84-95: Trim the serialNumber from the Message before doing
required/uniqueness checks in PhysicalServerApiInterceptor: retrieve
msg.getSerialNumber(), call trim() into a local variable (e.g., trimmedSerial),
then use trimmedSerial (not msg.getSerialNumber()) for the null/empty check and
in the Q.New(PhysicalServerVO.class) .eq(PhysicalServerAO_.serialNumber, ...)
existence query and in the ApiMessageInterceptionException message; ensure you
treat a trimmed empty string as missing and enforce uniqueness per zone using
the trimmed value.
- Around line 122-142: In PhysicalServerApiInterceptor, sanitize inputs by
trimming msg.getType() and all DHCP-related fields before using them: obtain
trimmedType = safeTrim(msg.getType()) and
trimmedDhcpInterface/trimmedDhcpRangeStartIp/trimmedDhcpRangeEndIp/trimmedDhcpRangeNetmask
from msg, use trimmedType for the "GATEWAY_PXE" equality check and use the
trimmed DHCP values when checking emptiness and when building the missing list;
throw the same ApiMessageInterceptionException/argerr with String.join on the
trimmed missing names so error messages do not include invisible whitespace.

In
`@test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy`:
- Around line 20-22: 注释中写的包路径 header.core.encrypt 与实际类所在包不一致;把注释中提到的
PasswordConverter 和 EncryptFacade 的包路径改为实际迁移后的包
org.zstack.header.convert(或直接移除错误的包名),并同时确认注释中提到的 PhysicalServerAO 的
`@Convert`(PasswordConverter.class) 描述与当前代码匹配,以避免误导定位。

---

Nitpick comments:
In
`@plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java`:
- Line 194: The helper method persistHardwareInfo(String serverUuid,
UnifiedHardwareInfo info) currently has package-private visibility but is
declared among private helpers; change its visibility to private to tighten
encapsulation by updating the method signature of persistHardwareInfo to be
private so it cannot be called from outside the class.
- Around line 77-82: The method mergeNonNull currently returns a boolean but its
return value is no longer used (calls like mergeNonNull(merged, runExt(...))
ignore the result); change mergeNonNull's signature to return void and update
its implementation accordingly, then update all call sites (e.g. the sequence
with runExt(SOURCE_IPMI_FRU, server), runExt(SOURCE_KVM_AGENT, server),
runExt(SOURCE_K8S_NODEINFO, server) and subsequent
persistHardwareInfo(serverUuid, merged)) to treat it as a void helper; also
update the method's Javadoc/comments (and any mention of discoverSource
semantics) to reflect the new void contract so the behavior and intent are clear
to future maintainers.
🪄 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: f19b4733-8c63-4290-ab5b-5e953f38f997

📥 Commits

Reviewing files that changed from the base of the PR and between 94b8d12 and e424b7e.

⛔ Files ignored due to path filters (1)
  • conf/springConfigXml/encrypt.xml is excluded by !**/*.xml
📒 Files selected for processing (25)
  • conf/db/upgrade/V5.5.18__schema.sql
  • core/src/main/java/org/zstack/core/convert/SpecialDataConverter.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacadeImpl.java
  • docs/brainstorms/2026-05-12-followups.md
  • docs/brainstorms/archives/2026-05-11-assertion-changelog.md
  • docs/brainstorms/archives/2026-05-11-case-fix-log.md
  • docs/brainstorms/next-session.md
  • docs/review/v5518-ut-on-bypass-audit.md
  • docs/todolist.md
  • header/src/main/java/org/zstack/header/convert/EncryptFacade.java
  • header/src/main/java/org/zstack/header/convert/PasswordConverter.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerAO.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java
  • plugin/ceph/src/main/java/org/zstack/storage/ceph/CephMonAO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostVO.java
  • plugin/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/PhysicalServerHardwareService.java
  • plugin/physicalServer/src/main/java/org/zstack/server/hardware/UnifiedHardwareInfo.java
  • plugin/physicalServer/src/test/java/org/zstack/server/hardware/UnifiedHardwareInfoMergeTest.java
  • plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerInterceptorErrorsCase.groovy
  • test/src/test/groovy/org/zstack/test/integration/server/PhysicalServerOobPasswordEncryptCase.groovy
💤 Files with no reviewable changes (4)
  • core/src/main/java/org/zstack/core/encrypt/EncryptFacade.java
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareDiscoveryExtensionPoint.java
  • conf/db/upgrade/V5.5.18__schema.sql
  • header/src/main/java/org/zstack/header/server/PhysicalServerHardwareInfoVO.java
✅ Files skipped from review due to trivial changes (6)
  • plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/SftpBackupStorageVO.java
  • test/src/test/groovy/org/zstack/test/integration/kvm/host/HostPasswordEncryptCase.groovy
  • core/src/main/java/org/zstack/core/convert/SpecialDataConverter.java
  • docs/brainstorms/next-session.md
  • docs/brainstorms/archives/2026-05-11-assertion-changelog.md
  • plugin/physicalServer/src/test/java/org/zstack/server/hardware/UnifiedHardwareInfoMergeTest.java

Comment on lines +48 to +59
private void validate(APIAttachPhysicalServerRoleMsg msg) {
boolean exists = Q.New(PhysicalServerRoleVO.class)
.eq(PhysicalServerRoleVO_.serverUuid, msg.getServerUuid())
.eq(PhysicalServerRoleVO_.roleType, msg.getRoleType())
.isExists();
if (exists) {
throw new ApiMessageInterceptionException(argerr(
"ORG_ZSTACK_PHYSICAL_SERVER_ROLE_DUPLICATE",
"PhysicalServer[uuid:%s] already has role[type:%s] attached; detach first or pick a different roleType",
msg.getServerUuid(), msg.getRoleType()));
}
}
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

对 roleType 和 serverUuid 参数执行 trim 后再进行查询和错误消息拼接。

用户在浏览器中复制粘贴时可能携带空格或换行符,直接使用 msg.getRoleType()msg.getServerUuid() 会导致重复性检查失效或错误消息中包含不可见字符。

🔧 建议修改
 private void validate(APIAttachPhysicalServerRoleMsg msg) {
+    String serverUuid = msg.getServerUuid() == null ? null : msg.getServerUuid().trim();
+    String roleType = msg.getRoleType() == null ? null : msg.getRoleType().trim();
+
     boolean exists = Q.New(PhysicalServerRoleVO.class)
-        .eq(PhysicalServerRoleVO_.serverUuid, msg.getServerUuid())
-        .eq(PhysicalServerRoleVO_.roleType, msg.getRoleType())
+        .eq(PhysicalServerRoleVO_.serverUuid, serverUuid)
+        .eq(PhysicalServerRoleVO_.roleType, roleType)
         .isExists();
     if (exists) {
         throw new ApiMessageInterceptionException(argerr(
             "ORG_ZSTACK_PHYSICAL_SERVER_ROLE_DUPLICATE",
             "PhysicalServer[uuid:%s] already has role[type:%s] attached; detach first or pick a different roleType",
-            msg.getServerUuid(), msg.getRoleType()));
+            serverUuid, roleType));
     }
 }

As per coding guidelines **/*Interceptor.java: 注意检查来自 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/physicalServer/src/main/java/org/zstack/server/PhysicalServerApiInterceptor.java`
around lines 48 - 59, The validate(APIAttachPhysicalServerRoleMsg msg) method
uses msg.getServerUuid() and msg.getRoleType() directly; trim both values (e.g.,
assign String serverUuid = msg.getServerUuid().trim(); String roleType =
msg.getRoleType().trim()) before using them in the
Q.New(PhysicalServerRoleVO.class) query (PhysicalServerRoleVO_.serverUuid,
PhysicalServerRoleVO_.roleType) and when building the
ApiMessageInterceptionException via argerr so the existence check and the error
message use the normalized (trimmed) values.

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.

2 participants