Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough此PR引入并实现虚拟机元数据子系统:消息、API、DTO/转换/序列化、敏感字段加密、元数据读写在 hypervisor/主存储路径、时间型垃圾回收与拦截器、以及 SDK/测试模拟支持。 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as API Layer
participant Primary as PrimaryStorage
participant Hypervisor as KVMHost
participant DB as Database
Client->>API: APIRegisterVmInstanceMsg (POST /vm-instances/register)
API->>Primary: handle(APIRegisterVmInstanceMsg)
Primary->>Hypervisor: ReadVmInstanceMetadataOnHypervisorMsg
Hypervisor-->>Primary: ReadVmInstanceMetadataOnHypervisorReply (metadata JSON)
Primary->>DB: persist volumes / snapshots / vm (convert & save)
Primary-->>API: APIRegisterVmInstanceReply (inventory)
API-->>Client: reply
sequenceDiagram
participant API as API Layer
participant Vm as VmInstanceBase
participant Primary as PrimaryStorage
participant Hypervisor as KVMHost
participant GC as UpdateVmInstanceMetadataGC
API->>Vm: UpdateVmInstanceMetadataMsg
Vm->>Primary: UpdateVmInstanceMetadataOnPrimaryStorageMsg (包含 metadata, rootVolumeUuid)
Primary->>Hypervisor: UpdateVmInstanceMetadataOnHypervisorMsg
Hypervisor-->>Primary: UpdateVmInstanceMetadataOnHypervisorReply
Primary-->>Vm: UpdateVmInstanceMetadataReply
alt hypervisor failure
Vm->>GC: 提交/更新 UpdateVmInstanceMetadataGC(持久化重试)
GC->>GC: triggerNow() -> 重试/指数回退
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In `@compute/src/main/java/org/zstack/compute/vm/UpdateVmInstanceMetadataGC.java`:
- Around line 35-40: There is a TOCTOU race between checking
chainInfo.getPendingTask().isEmpty() (from thdf.getChainTaskInfo(...)) and
actually submitting the GC task (e.g. chainSubmit), so add a second verification
just before submission: re-fetch ChainInfo via
thdf.getChainTaskInfo(getUpdateVmInstanceMetadataSyncSignature(vmInstanceUuid))
and only proceed with the submission if getPendingTask().isEmpty() still holds
otherwise call completion.cancel(); alternatively document that duplicate
submissions are acceptable for GC; update the code paths around
getUpdateVmInstanceMetadataSyncSignature, thdf.getChainTaskInfo,
completion.cancel and the chain submission site to perform this double-check or
explicit synchronization.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Line 24: Remove the unused import of APIRegisterVmInstanceMsg from
VmInstanceApiInterceptor: open the VmInstanceApiInterceptor class, locate the
import statement "import
org.zstack.header.storage.primary.APIRegisterVmInstanceMsg" and delete it (the
intercept() method in VmInstanceApiInterceptor does not handle this message and
APIRegisterVmInstanceMsg is handled by PrimaryStorageBase), leaving only the
imports actually used by the class.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java`:
- Around line 9399-9411: In the bus.send callback (run method) that builds an
UpdateVmInstanceMetadataOnPrimaryStorageReply, ensure the failure branch calls
bus.reply(msg, reply) before invoking submitUpdateVmInstanceMetadataGC();
specifically, in the run override inside VmInstanceBase where you create the
reply on !r.isSuccess(), set the error on reply and immediately call
bus.reply(msg, reply) (using the same msg and reply variables) and only then
call submitUpdateVmInstanceMetadataGC() so the caller is replied to and won't
hang.
- Around line 9389-9392: The code assumes Tuple tuple =
Q.New(VolumeVO.class)...findTuple() always returns non-null; if tuple is null
the subsequent tuple.get(...) causes NPE. Fix: after obtaining tuple in
VmInstanceBase (the block that queries VolumeVO/VolumeVO_/VolumeType.Root with
msg.getUuid()), check if tuple == null and if so construct and send an error
reply and return (do not proceed to read primaryStorageUuid/rootVolumeUuid);
ensure the error contains context (vm uuid) and use the normal reply/error path
used elsewhere in this class.
In `@core/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.java`:
- Around line 287-291: 在 handle(SubmitTimeBasedGarbageCollectorMsg) 中对
msg.getGcInterval()(Long)和 msg.getUnit() 做非空与范围校验,避免自动拆箱 NPE 和无效调度:如果 gcInterval
为 null 或 gcInterval <= 0 或 unit 为 null,则不要调用 msg.getGc().submit(...); 构造并通过
bus.reply(msg, reply) 返回一个错误 MessageReply(含描述性错误信息),否则正常调用
msg.getGc().submit(msg.getGcInterval(), msg.getUnit()) 并返回成功回复;参照方法和字段名:handle,
SubmitTimeBasedGarbageCollectorMsg, getGc(), getGcInterval(), getUnit(), submit。
In
`@core/src/main/java/org/zstack/core/gc/SubmitTimeBasedGarbageCollectorMsg.java`:
- Around line 7-10: SubmitTimeBasedGarbageCollectorMsg currently includes a
TimeBasedGarbageCollector instance which breaks cross-node Gson serialization;
replace the gc field with a serializable identifier (e.g. String gcUuid or
String gcClassName) and any minimal state (gcInterval, unit), and update
send/receive flows to reconstruct the GC on the receiver side (use a GC
registry/factory or Spring bean lookup to create the TimeBasedGarbageCollector
such as UpdateVmInstanceMetadataGC instead of serializing
ThreadFacade-containing instances). Locate SubmitTimeBasedGarbageCollectorMsg
(field gc), the send path that calls makeTargetServiceIdByResourceUuid(), and
the message handler that schedules the GC, and change them to pass the
identifier and call your TimeBasedGarbageCollectorFactory/manager to rehydrate
the concrete GC before scheduling.
In
`@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy`:
- Around line 10-23: Update the documentation entries for the
APIRegisterVmInstanceEvent: replace the placeholder desc "null" under the ref
block for name "inventory" (path
"org.zstack.header.storage.primary.APIRegisterVmInstanceEvent.inventory", clz
VmInstanceInventory.class) with a concise Chinese description of the
VmInstanceInventory (e.g., “虚拟机实例信息” plus any brief detail), and fill the field
block for name "success" (type "boolean") with a short Chinese description
indicating it denotes whether the operation succeeded (e.g., “操作是否成功”); keep the
descriptions brief and context-appropriate.
In
`@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java`:
- Around line 26-27: 在 APIRegisterVmInstanceMsg 中,给 metadataPath 明确声明约束并在
__example__ 中填充示例值:将字段上的注解由 `@APIParam`() 改为 `@APIParam`(required = true)
以显式表明必需性,并在类的 __example__ 方法里通过对应的 setter(例如 setMetadataPath(...))为 metadataPath
赋予一个合理的示例值,确保生成的 API 文档包含所有必填字段的示例。
In
`@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy`:
- Around line 49-57: Add a human-readable Chinese description for the
metadataPath parameter in APIRegisterVmInstanceMsgDoc_zh_cn.groovy by filling
the empty desc field inside the column block for "metadataPath"; mention the
parameter's purpose (path or URL to VM metadata), expected format (e.g.,
absolute path or URL, allowed schemes like http(s) or file, any encoding/size
constraints) and whether it is required, and include since "4.10.0" context —
update the desc to a concise sentence that guides API users on what to provide
and any validation rules.
In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java`:
- Around line 4720-4733: Add the `@NoLogging` annotation to the sensitive metadata
fields to prevent them from being written to logs: annotate the 'metadata' field
in class WriteVmInstanceMetadataCmd and the 'metadata' field in class
ReadVmInstanceMetadataRsp with `@NoLogging`, and import the NoLogging annotation
(where other agent DTO annotations are defined) so the serializer/logging
framework will skip these fields.
In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java`:
- Around line 7382-7388: In the success handler for ReadVmInstanceMetadataRsp
(the anonymous success(ReadVmInstanceMetadataRsp rsp) method), avoid setting
reply metadata when rsp.isSuccess() is false: only call
reply.setMetadata(rsp.metadata) inside the success branch; when rsp.isSuccess()
is false set the error on reply (reply.setError(...)) and do not set metadata
before bus.reply(msg, reply) and completion.done(); update the success(...)
logic accordingly so metadata is only populated on successful responses.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3800-3824: The code calls findValue() into installPath and
immediately uses replaceFirst causing an NPE if installPath is null; also the
error reply uses self.getUuid() (primary storage) instead of the root volume or
VM id. Update the handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...) to
check if installPath == null (or blank) before calling replaceFirst; if null,
create an UpdateVmInstanceMetadataOnPrimaryStorageReply, setError using
Platform.operr(...) that references msg.getRootVolumeUuid() (or
msg.getVmInstanceUuid() if available) and include the underlying cause/context,
then bus.reply(msg, reply) and return. Keep the rest of the flow (creating
metadataPath, sending UpdateVmInstanceMetadataOnHypervisorMsg) unchanged.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1928-1951: The handler
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg) must null-check the backend
returned by getUsableBackend() and should select the backend based on the host
UUID to avoid NPEs and wrong routing; change the logic to call
getHostUuidFromVolume(msg.getRootVolumeUuid()), then obtain a backend via a
host-aware selector (e.g. a new/getBackendForHost(hostUuid) or similar lookup)
instead of blindly using getUsableBackend(), validate that the returned
NfsPrimaryStorageBackend is non-null and return an error reply if it is null,
and only then call backend.handle(msg, hostUuid, new ReturnValueCompletion...).
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2052-2067: The callback in
NfsPrimaryStorageKVMBackend.handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...)
is replying with the wrong reply type and never completing the provided
ReturnValueCompletion; change the CloudBusCallBack to construct and send an
UpdateVmInstanceMetadataOnPrimaryStorageReply and call completion.success(reply)
on success or completion.fail(error) on failure (use the hypervisor MessageReply
error via r.getError()), and stop using
RevertVolumeFromSnapshotOnPrimaryStorageReply or bus.reply(msg, ...) so the
caller's completion is properly resolved and the UUID in error messages is the
vm/primary storage UUID from msg.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1870-2134: The registerVmInstance flow chain currently uses only
NoRollbackFlow steps (read-metadata, register-volume, register-snapshot,
register-vmInstance) and persists entities via dbf.persist/dbf.persistCollection
without compensation, leaving stale
VolumeVO/VolumeSnapshotVO/VolumeSnapshotTreeVO/VolumeSnapshotGroupVO/VolumeSnapshotGroupRefVO/VmInstanceVO
rows on failure; change the persisting flows to support rollback by either (a)
switch those NoRollbackFlow instances to Flow and implement rollback handlers
that delete any entities created in that flow (track created VO UUIDs in
flow-local lists) or (b) wrap the whole chain in a single
transactional/compensating flow that deletes all created entities on error (use
the collected created lists from register-volume, register-snapshot,
register-vmInstance and remove via dbf.remove or appropriate DAO methods in the
Flow.rollback), ensuring completion/error handlers remain unchanged so
completion.fail is called on error.
- Around line 1837-1867: The fail branch in handle(APIRegisterVmInstanceMsg msg)
publishes the APIRegisterVmInstanceEvent without setting the error, so callers
may think the operation succeeded; in the
ReturnValueCompletion<VmInstanceInventory>.fail(ErrorCode errorCode)
implementation, call event.setError(errorCode) (or set an appropriate error on
the APIRegisterVmInstanceEvent) before bus.publish(event) and chain.next() so
the failure is propagated back to the caller when registerVmInstance fails.
🧹 Nitpick comments (7)
testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy (1)
684-691: 建议将超长 JSON 响应外置或构建生成。当前内联字符串过长,维护和阅读成本高;可考虑放到资源文件或用结构化对象再序列化生成。
sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java (2)
5-5: 冗余的同包导入。
import org.zstack.sdk.*;是冗余的,因为该类本身就在org.zstack.sdk包中。♻️ 建议移除冗余导入
import java.util.HashMap; import java.util.Map; -import org.zstack.sdk.*;
43-47: 建议使用泛型参数化 List 类型。
systemTags和userTags使用了原始类型List,建议参数化为List<String>以提高类型安全性。♻️ 建议的类型参数化
`@Param`(required = false) - public java.util.List systemTags; + public java.util.List<String> systemTags; `@Param`(required = false) - public java.util.List userTags; + public java.util.List<String> userTags;header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java (1)
5-14: 建议字段命名保持一致性。
ReadVmInstanceMetadataOnHypervisorReply使用字段名metadata,而ReadVmInstanceMetadataReply使用vmMetadata。建议统一字段命名以提高代码可维护性和数据映射的清晰度。♻️ 建议的修改
public class ReadVmInstanceMetadataOnHypervisorReply extends MessageReply { - private String metadata; + private String vmMetadata; - public String getMetadata() { - return metadata; + public String getVmMetadata() { + return vmMetadata; } - public void setMetadata(String metadata) { - this.metadata = metadata; + public void setVmMetadata(String vmMetadata) { + this.vmMetadata = vmMetadata; } }storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
2138-2185: 反射读取静态字段不应依赖 clazz.newInstance()。
newInstance()已废弃且要求 public 无参构造;对 static 字段可用field.get(null)。建议改为getDeclaredConstructor().newInstance()(并处理异常)或按是否 static 取值。header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEvent.java (1)
24-25: 建议与现有 API Event 保持一致的构造函数风格参考
APIReimageVmInstanceEvent的无参构造函数实现:public APIReimageVmInstanceEvent() { super(null); }当前实现虽然功能上没有问题,但建议保持一致性。
♻️ 可选的修改建议
public APIRegisterVmInstanceEvent() { + super(null); }header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java (1)
70-72:__example__()返回空对象,文档示例信息不足。建议填充代表性字段值,提升自动生成的 API 文档可读性与可用性。
♻️ 建议示例填充
public static APIRegisterVmInstanceMsg __example__() { APIRegisterVmInstanceMsg msg = new APIRegisterVmInstanceMsg(); + msg.setZoneUuid(uuid()); + msg.setClusterUuid(uuid()); + msg.setPrimaryStorageUuid(uuid()); + msg.setHostUuid(uuid()); + msg.setMetadataPath("/zstack/metadata/vm-uuid"); return msg; }
| String queueName = getUpdateVmInstanceMetadataSyncSignature(vmInstanceUuid); | ||
| ChainInfo chainInfo = thdf.getChainTaskInfo(queueName); | ||
| if (!chainInfo.getPendingTask().isEmpty()) { | ||
| completion.cancel(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
潜在的 TOCTOU 竞态条件
在检查 chainInfo.getPendingTask().isEmpty() 和提交 chainSubmit 之间存在时间窗口,其他线程可能在此期间提交任务。不过考虑到这是 GC 场景,偶尔的重复提交不会造成数据一致性问题(因为后续会通过 syncSignature 串行化执行),影响较小。
建议确认这种行为是否符合预期设计。
🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/UpdateVmInstanceMetadataGC.java`
around lines 35 - 40, There is a TOCTOU race between checking
chainInfo.getPendingTask().isEmpty() (from thdf.getChainTaskInfo(...)) and
actually submitting the GC task (e.g. chainSubmit), so add a second verification
just before submission: re-fetch ChainInfo via
thdf.getChainTaskInfo(getUpdateVmInstanceMetadataSyncSignature(vmInstanceUuid))
and only proceed with the submission if getPendingTask().isEmpty() still holds
otherwise call completion.cancel(); alternatively document that duplicate
submissions are acceptable for GC; update the code paths around
getUpdateVmInstanceMetadataSyncSignature, thdf.getChainTaskInfo,
completion.cancel and the chain submission site to perform this double-check or
explicit synchronization.
| import org.zstack.header.message.APIMessage; | ||
| import org.zstack.header.network.l2.*; | ||
| import org.zstack.header.network.l3.*; | ||
| import org.zstack.header.storage.primary.APIRegisterVmInstanceMsg; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if APIRegisterVmInstanceMsg is used in VmInstanceApiInterceptor.java
rg -n "APIRegisterVmInstanceMsg" compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
# Also check the intercept method to see all handled message types
rg -n "instanceof.*Msg" compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java | head -30Repository: MatheMatrix/zstack
Length of output: 2170
🏁 Script executed:
#!/bin/bash
# Check if there's a handler method for APIRegisterVmInstanceMsg anywhere in the intercept method
cat -n compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java | sed -n '85,180p'
# Also check if the method signature and pattern continues beyond line 146
tail -n +140 compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java | head -50Repository: MatheMatrix/zstack
Length of output: 8697
🏁 Script executed:
#!/bin/bash
# Verify if APIRegisterVmInstanceMsg is used in other parts of the codebase
rg -r "APIRegisterVmInstanceMsg" compute/src/main/java/org/zstack/compute/vm/ --type java
# Check if there's any mention of RegisterVmInstance in other interceptor or handler files
rg -r "RegisterVmInstance" compute/src/main/java/org/zstack/compute/ --type java | head -20Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Double-check: search entire repository for APIRegisterVmInstanceMsg to be thorough
rg "APIRegisterVmInstanceMsg" --type javaRepository: MatheMatrix/zstack
Length of output: 1264
移除未使用的 APIRegisterVmInstanceMsg 导入。
该导入在 intercept() 方法中不存在对应的处理逻辑。APIRegisterVmInstanceMsg 实际由 PrimaryStorageBase 处理,与本文件无关。
🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java` at
line 24, Remove the unused import of APIRegisterVmInstanceMsg from
VmInstanceApiInterceptor: open the VmInstanceApiInterceptor class, locate the
import statement "import
org.zstack.header.storage.primary.APIRegisterVmInstanceMsg" and delete it (the
intercept() method in VmInstanceApiInterceptor does not handle this message and
APIRegisterVmInstanceMsg is handled by PrimaryStorageBase), leaving only the
imports actually used by the class.
| Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid) | ||
| .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple(); | ||
| String primaryStorageUuid = tuple.get(0, String.class); | ||
| String rootVolumeUuid = tuple.get(1, String.class); |
There was a problem hiding this comment.
补充根卷查询为空的兜底处理。
Line 9389-9392 若根卷不存在,findTuple() 可能返回 null,直接 tuple.get(...) 会 NPE。建议先返回错误并回复消息。
🛠️ 建议修复
- Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid)
- .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple();
- String primaryStorageUuid = tuple.get(0, String.class);
- String rootVolumeUuid = tuple.get(1, String.class);
+ Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid)
+ .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple();
+ if (tuple == null) {
+ UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
+ reply.setError(operr("cannot find root volume for vm[uuid:%s]", msg.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+ String primaryStorageUuid = tuple.get(0, String.class);
+ String rootVolumeUuid = tuple.get(1, String.class);🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines
9389 - 9392, The code assumes Tuple tuple = Q.New(VolumeVO.class)...findTuple()
always returns non-null; if tuple is null the subsequent tuple.get(...) causes
NPE. Fix: after obtaining tuple in VmInstanceBase (the block that queries
VolumeVO/VolumeVO_/VolumeType.Root with msg.getUuid()), check if tuple == null
and if so construct and send an error reply and return (do not proceed to read
primaryStorageUuid/rootVolumeUuid); ensure the error contains context (vm uuid)
and use the normal reply/error path used elsewhere in this class.
| private void handle(final SubmitTimeBasedGarbageCollectorMsg msg) { | ||
| MessageReply reply = new MessageReply(); | ||
| msg.getGc().submit(msg.getGcInterval(), msg.getUnit()); | ||
| bus.reply(msg, reply); | ||
| } |
There was a problem hiding this comment.
补充参数非空与范围校验,避免 NPE 与无效调度。
gcInterval 为 Long,为空时自动拆箱会抛 NPE;interval <= 0 也可能导致错误调度。建议在回复前校验并返回错误。
🛡️ 建议修复
private void handle(final SubmitTimeBasedGarbageCollectorMsg msg) {
MessageReply reply = new MessageReply();
- msg.getGc().submit(msg.getGcInterval(), msg.getUnit());
- bus.reply(msg, reply);
+ if (msg.getGc() == null || msg.getGcInterval() == null || msg.getUnit() == null) {
+ reply.setError(operr("gc/gcInterval/unit cannot be null"));
+ bus.reply(msg, reply);
+ return;
+ }
+ if (msg.getGcInterval() <= 0) {
+ reply.setError(operr("gcInterval must be > 0"));
+ bus.reply(msg, reply);
+ return;
+ }
+ msg.getGc().submit(msg.getGcInterval(), msg.getUnit());
+ bus.reply(msg, reply);
}🤖 Prompt for AI Agents
In `@core/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.java`
around lines 287 - 291, 在 handle(SubmitTimeBasedGarbageCollectorMsg) 中对
msg.getGcInterval()(Long)和 msg.getUnit() 做非空与范围校验,避免自动拆箱 NPE 和无效调度:如果 gcInterval
为 null 或 gcInterval <= 0 或 unit 为 null,则不要调用 msg.getGc().submit(...); 构造并通过
bus.reply(msg, reply) 返回一个错误 MessageReply(含描述性错误信息),否则正常调用
msg.getGc().submit(msg.getGcInterval(), msg.getUnit()) 并返回成功回复;参照方法和字段名:handle,
SubmitTimeBasedGarbageCollectorMsg, getGc(), getGcInterval(), getUnit(), submit。
| @Override | ||
| void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { | ||
| String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue(); | ||
| // /vms_ds/rootVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-829a91b68e794a03865eab8a5918600a/snapshots/f2c31aeede604917aa8cee24848d8bfa.qcow2 | ||
| // /vms_ds/rootVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-829a91b68e794a03865eab8a5918600a/829a91b68e794a03865eab8a5918600a.qcow2 | ||
|
|
||
| String path = installPath.replaceFirst("^(.+/vol-[^/]+/).*$", "$1"); | ||
| String metadataPath = String.format("%s%s", path, VM_META_SUFFIX); | ||
|
|
||
| UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); | ||
| umsg.setMetadata(msg.getMetadata()); | ||
| umsg.setMetadataPath(metadataPath); | ||
| umsg.setHostUuid(hostUuid); | ||
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); | ||
| bus.send(umsg, new CloudBusCallBack(msg) { | ||
| @Override | ||
| public void run(MessageReply r) { | ||
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | ||
| if (!r.isSuccess()) { | ||
| reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) | ||
| .withCause(r.getError())); | ||
| } | ||
| bus.reply(msg, reply); | ||
| } | ||
| }); |
There was a problem hiding this comment.
未校验 root volume installPath,可能触发 NPE。
findValue() 为空时 replaceFirst 会 NPE;同时错误信息使用了 primary storage uuid。建议先判空并用 rootVolumeUuid/VM uuid 提示。
🔧 建议修复
- String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue();
+ String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue();
+ final UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
+ if (installPath == null) {
+ reply.setError(operr("cannot find root volume[uuid:%s] installPath", msg.getRootVolumeUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
@@
- bus.send(umsg, new CloudBusCallBack(msg) {
+ bus.send(umsg, new CloudBusCallBack(msg) {
`@Override`
public void run(MessageReply r) {
- UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
if (!r.isSuccess()) {
- reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid())
+ reply.setError(Platform.operr("failed to update vm[rootVolumeUuid=%s] on hypervisor.", msg.getRootVolumeUuid())
.withCause(r.getError()));
}
bus.reply(msg, reply);
}
});🤖 Prompt for AI Agents
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3800 - 3824, The code calls findValue() into installPath and
immediately uses replaceFirst causing an NPE if installPath is null; also the
error reply uses self.getUuid() (primary storage) instead of the root volume or
VM id. Update the handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...) to
check if installPath == null (or blank) before calling replaceFirst; if null,
create an UpdateVmInstanceMetadataOnPrimaryStorageReply, setError using
Platform.operr(...) that references msg.getRootVolumeUuid() (or
msg.getVmInstanceUuid() if available) and include the underlying cause/context,
then bus.reply(msg, reply) and return. Keep the rest of the flow (creating
metadataPath, sending UpdateVmInstanceMetadataOnHypervisorMsg) unchanged.
| protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | ||
|
|
||
| String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid()); | ||
| if (hostUuid == null || hostUuid.isEmpty()) { | ||
| reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid())); | ||
| bus.reply(msg, reply); | ||
| return; | ||
| } | ||
|
|
||
| final NfsPrimaryStorageBackend backend = getUsableBackend(); | ||
|
|
||
| backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) { | ||
| @Override | ||
| public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) { | ||
| bus.reply(msg, r); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| reply.setError(errorCode); | ||
| bus.reply(msg, reply); | ||
| } | ||
| }); |
There was a problem hiding this comment.
backend 选择与空值校验缺失,存在 NPE/路由错误风险。
这里直接使用 getUsableBackend() 且无空值检查;若无可用 backend 会直接 NPE。同时建议基于 hostUuid 选择 backend,以避免多 hypervisor 场景走错实现。
✅ 建议修复
- final NfsPrimaryStorageBackend backend = getUsableBackend();
+ final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid);
+ if (backend == null) {
+ reply.setError(operr("no backend found for host[uuid:%s]", hostUuid));
+ bus.reply(msg, reply);
+ return;
+ }
backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) {
`@Override`
public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) {
bus.reply(msg, r);
}📝 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.
| protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { | |
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | |
| String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid()); | |
| if (hostUuid == null || hostUuid.isEmpty()) { | |
| reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid())); | |
| bus.reply(msg, reply); | |
| return; | |
| } | |
| final NfsPrimaryStorageBackend backend = getUsableBackend(); | |
| backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) { | |
| @Override | |
| public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) { | |
| bus.reply(msg, r); | |
| } | |
| @Override | |
| public void fail(ErrorCode errorCode) { | |
| reply.setError(errorCode); | |
| bus.reply(msg, reply); | |
| } | |
| }); | |
| protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { | |
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | |
| String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid()); | |
| if (hostUuid == null || hostUuid.isEmpty()) { | |
| reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid())); | |
| bus.reply(msg, reply); | |
| return; | |
| } | |
| final NfsPrimaryStorageBackend backend = getUsableBackend(); | |
| if (backend == null) { | |
| reply.setError(operr("no backend found for volume[uuid:%s]", msg.getRootVolumeUuid())); | |
| bus.reply(msg, reply); | |
| return; | |
| } | |
| backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) { | |
| `@Override` | |
| public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) { | |
| bus.reply(msg, r); | |
| } | |
| `@Override` | |
| public void fail(ErrorCode errorCode) { | |
| reply.setError(errorCode); | |
| bus.reply(msg, reply); | |
| } | |
| }); | |
| } |
🤖 Prompt for AI Agents
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`
around lines 1928 - 1951, The handler
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg) must null-check the backend
returned by getUsableBackend() and should select the backend based on the host
UUID to avoid NPEs and wrong routing; change the logic to call
getHostUuidFromVolume(msg.getRootVolumeUuid()), then obtain a backend via a
host-aware selector (e.g. a new/getBackendForHost(hostUuid) or similar lookup)
instead of blindly using getUsableBackend(), validate that the returned
NfsPrimaryStorageBackend is non-null and return an error reply if it is null,
and only then call backend.handle(msg, hostUuid, new ReturnValueCompletion...).
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { | ||
| UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); | ||
| umsg.setMetadata(msg.getMetadata()); | ||
| umsg.setHostUuid(hostUuid); | ||
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); | ||
| bus.send(umsg, new CloudBusCallBack(msg) { | ||
| @Override | ||
| public void run(MessageReply r) { | ||
| RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); | ||
| if (!r.isSuccess()) { | ||
| reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", msg.getPrimaryStorageUuid()) | ||
| .withCause(r.getError())); | ||
| } | ||
| bus.reply(msg, reply); | ||
| } | ||
| }); |
There was a problem hiding this comment.
回调路径与回复类型不匹配,导致上层永远不完成。
当前实现忽略 completion 且使用了错误的 reply 类型,容易造成上层等待超时,并且错误信息中的 UUID 也不准确。请改为通过 completion.success/fail 传递结果并使用正确的 reply 类型。
✅ 建议修复
public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) {
UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg();
umsg.setMetadata(msg.getMetadata());
umsg.setHostUuid(hostUuid);
bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid);
bus.send(umsg, new CloudBusCallBack(msg) {
`@Override`
public void run(MessageReply r) {
- RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply();
if (!r.isSuccess()) {
- reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", msg.getPrimaryStorageUuid())
- .withCause(r.getError()));
+ completion.fail(Platform.operr("failed to update vm metadata for root volume[uuid=%s] on hypervisor.",
+ msg.getRootVolumeUuid()).withCause(r.getError()));
+ return;
}
- bus.reply(msg, reply);
+ completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply());
}
});
}📝 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.
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { | |
| UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); | |
| umsg.setMetadata(msg.getMetadata()); | |
| umsg.setHostUuid(hostUuid); | |
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); | |
| bus.send(umsg, new CloudBusCallBack(msg) { | |
| @Override | |
| public void run(MessageReply r) { | |
| RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); | |
| if (!r.isSuccess()) { | |
| reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", msg.getPrimaryStorageUuid()) | |
| .withCause(r.getError())); | |
| } | |
| bus.reply(msg, reply); | |
| } | |
| }); | |
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { | |
| UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); | |
| umsg.setMetadata(msg.getMetadata()); | |
| umsg.setHostUuid(hostUuid); | |
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); | |
| bus.send(umsg, new CloudBusCallBack(msg) { | |
| `@Override` | |
| public void run(MessageReply r) { | |
| if (!r.isSuccess()) { | |
| completion.fail(Platform.operr("failed to update vm metadata for root volume[uuid=%s] on hypervisor.", | |
| msg.getRootVolumeUuid()).withCause(r.getError())); | |
| return; | |
| } | |
| completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); | |
| } | |
| }); | |
| } |
🤖 Prompt for AI Agents
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`
around lines 2052 - 2067, The callback in
NfsPrimaryStorageKVMBackend.handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...)
is replying with the wrong reply type and never completing the provided
ReturnValueCompletion; change the CloudBusCallBack to construct and send an
UpdateVmInstanceMetadataOnPrimaryStorageReply and call completion.success(reply)
on success or completion.fail(error) on failure (use the hypervisor MessageReply
error via r.getError()), and stop using
RevertVolumeFromSnapshotOnPrimaryStorageReply or bus.reply(msg, ...) so the
caller's completion is properly resolved and the UUID in error messages is the
vm/primary storage UUID from msg.
| private void handle(APIRegisterVmInstanceMsg msg) { | ||
| APIRegisterVmInstanceEvent event = new APIRegisterVmInstanceEvent(msg.getId()); | ||
| thdf.chainSubmit(new ChainTask(msg) { | ||
| @Override | ||
| public String getSyncSignature() { | ||
| return String.format("register-vm-from-%s", msg.getMetadataPath()); | ||
| } | ||
|
|
||
| @Override | ||
| public void run(SyncTaskChain chain) { | ||
| registerVmInstance(msg, new ReturnValueCompletion<VmInstanceInventory>(chain, msg) { | ||
| @Override | ||
| public void success(VmInstanceInventory vmInstanceInventory) { | ||
| event.setInventory(vmInstanceInventory); | ||
| bus.publish(event); | ||
| chain.next(); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| bus.publish(event); | ||
| chain.next(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return String.format("register-vm-from-%s", msg.getMetadataPath()); | ||
| } | ||
| }); |
There was a problem hiding this comment.
失败分支未设置 event 错误。
当前 fail 只 publish event,调用方可能被视为成功;应设置 error。
🔧 建议修复
- public void fail(ErrorCode errorCode) {
- bus.publish(event);
- chain.next();
- }
+ public void fail(ErrorCode errorCode) {
+ event.setError(errorCode);
+ bus.publish(event);
+ chain.next();
+ }🤖 Prompt for AI Agents
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1837 - 1867, The fail branch in handle(APIRegisterVmInstanceMsg
msg) publishes the APIRegisterVmInstanceEvent without setting the error, so
callers may think the operation succeeded; in the
ReturnValueCompletion<VmInstanceInventory>.fail(ErrorCode errorCode)
implementation, call event.setError(errorCode) (or set an appropriate error on
the APIRegisterVmInstanceEvent) before bus.publish(event) and chain.next() so
the failure is propagated back to the caller when registerVmInstance fails.
| private void registerVmInstance(APIRegisterVmInstanceMsg msg, ReturnValueCompletion<VmInstanceInventory> completion) { | ||
| FlowChain chain = new ShareFlowChain(); | ||
| chain.setName("register-vm-from-metadata"); | ||
| chain.then(new ShareFlow() { | ||
| VmMetadata vmMetadata; | ||
| VmInstanceInventory vmInstanceInventory; | ||
|
|
||
| @Override | ||
| public void setup() { | ||
| flow(new NoRollbackFlow() { | ||
| String __name__ = "read-metadata"; | ||
|
|
||
| @Override | ||
| public void run(FlowTrigger trigger, Map data) { | ||
| ReadVmInstanceMetadataOnHypervisorMsg umsg = new ReadVmInstanceMetadataOnHypervisorMsg(); | ||
| umsg.setHostUuid(msg.getHostUuid()); | ||
| umsg.setMetadataPath(msg.getMetadataPath()); | ||
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, msg.getHostUuid()); | ||
| bus.send(umsg, new CloudBusCallBack(msg) { | ||
| @Override | ||
| public void run(MessageReply r) { | ||
| if (!r.isSuccess()) { | ||
| trigger.fail(operr("failed to update vm[uuid=%s] on hypervisor.", | ||
| self.getUuid()).withCause(r.getError())); | ||
| return; | ||
| } | ||
| ReadVmInstanceMetadataOnHypervisorReply reply = r.castReply(); | ||
| vmMetadata = JSONObjectUtil.toObject(reply.getMetadata(), VmMetadata.class); | ||
| trigger.next(); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| flow(new NoRollbackFlow() { | ||
| String __name__ = "register-volume"; | ||
|
|
||
| @Override | ||
| public void run(FlowTrigger trigger, Map data) { | ||
| List<String> volumesString = vmMetadata.volumeVOs; | ||
|
|
||
| List<VolumeVO> volumes = new ArrayList<>(); | ||
| volumesString.forEach(v -> volumes.add(JSONObjectUtil.toObject(v, VolumeVO.class))); | ||
|
|
||
| List<VolumeVO> newVolumes = new ArrayList<>(); | ||
| volumes.forEach(v -> { | ||
| VolumeVO vo = new VolumeVO(); | ||
| // vo.setRootImageUuid(vo.getRootImageUuid()); | ||
| vo.setAccountUuid(msg.getSession().getAccountUuid()); | ||
| vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); | ||
| vo.setInstallPath(v.getInstallPath()); | ||
|
|
||
| vo.setCreateDate(v.getCreateDate()); | ||
| vo.setDescription(v.getDescription()); | ||
| vo.setName(v.getName()); | ||
| vo.setSize(v.getSize()); | ||
| vo.setActualSize(v.getActualSize()); | ||
| vo.setState(v.getState()); | ||
| vo.setUuid(v.getUuid()); | ||
| vo.setVmInstanceUuid(v.getVmInstanceUuid()); | ||
| vo.setType(v.getType()); | ||
| vo.setCreateDate(v.getCreateDate()); | ||
| vo.setLastOpDate(v.getLastOpDate()); | ||
| vo.setDeviceId(v.getDeviceId()); | ||
| vo.setStatus(v.getStatus()); | ||
| vo.setFormat(v.getFormat()); | ||
| vo.setShareable(v.isShareable()); | ||
| vo.setVolumeQos(v.getVolumeQos()); | ||
| vo.setLastDetachDate(v.getLastDetachDate()); | ||
| vo.setLastVmInstanceUuid(v.getLastVmInstanceUuid()); | ||
| vo.setLastAttachDate(v.getLastAttachDate()); | ||
| vo.setProtocol(v.getProtocol()); | ||
| newVolumes.add(vo); | ||
| }); | ||
| dbf.persistCollection(newVolumes); | ||
| trigger.next(); | ||
| } | ||
| }); | ||
|
|
||
| flow(new NoRollbackFlow() { | ||
| String __name__ = "register-snapshot"; | ||
|
|
||
| @Override | ||
| public void run(FlowTrigger trigger, Map data) { | ||
| // 快照 | ||
| vmMetadata.volumeSnapshots.forEach((volumeUuid, snapshotList) -> { | ||
| // 一个 volume 有多个快照树 | ||
| // key = treeuuid | ||
| // value = snapshosts | ||
| Map<String, List<VolumeSnapshotInventory>> snapshotsByTreeUuid = new HashMap<>(); | ||
| snapshotList.forEach(snapshot -> { | ||
| VolumeSnapshotInventory inv = JSONObjectUtil.toObject(snapshot, VolumeSnapshotInventory.class); | ||
| if (snapshotsByTreeUuid.containsKey(inv.getTreeUuid())) { | ||
| snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); | ||
| } else { | ||
| snapshotsByTreeUuid.put(inv.getTreeUuid(), new ArrayList<>()); | ||
| snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); | ||
| } | ||
| }); | ||
|
|
||
| // 遍历每一颗树 | ||
| snapshotsByTreeUuid.forEach((treeUuid, snapshots) -> { | ||
| //构建快照树 | ||
| VolumeSnapshotTree tree = VolumeSnapshotTree.fromInventories(snapshots); | ||
| // 层级遍历 快照 | ||
| List<VolumeSnapshotInventory> levelOrderTraversals = tree.levelOrderTraversal(); | ||
| // 判断当前树有没有 latest 节点 | ||
| boolean treeIsCurrent = levelOrderTraversals.stream().anyMatch(VolumeSnapshotInventory::isLatest); | ||
|
|
||
| // 先创建快照树,VolumeSnapshotVO 外键依赖 VolumeSnapshotTreeVO | ||
| VolumeSnapshotTreeVO newTree = new VolumeSnapshotTreeVO(); | ||
| newTree.setCurrent(treeIsCurrent); | ||
| newTree.setVolumeUuid(volumeUuid); | ||
| newTree.setUuid(treeUuid); | ||
| newTree.setStatus(VolumeSnapshotTreeStatus.Completed); | ||
| dbf.persist(newTree); | ||
|
|
||
| // 按照层级遍历的快照构建VolumeSnapshotTreeVO | ||
| levelOrderTraversals.forEach(snapshot -> { | ||
| VolumeSnapshotVO vo = new VolumeSnapshotVO(); | ||
| vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); | ||
| vo.setPrimaryStorageInstallPath(snapshot.getPrimaryStorageInstallPath()); | ||
|
|
||
| vo.setName(snapshot.getName()); | ||
| vo.setCreateDate(snapshot.getCreateDate()); | ||
| vo.setDescription(snapshot.getDescription()); | ||
| vo.setLastOpDate(snapshot.getLastOpDate()); | ||
| vo.setParentUuid(snapshot.getParentUuid()); | ||
| vo.setState(VolumeSnapshotState.valueOf(snapshot.getState())); | ||
| vo.setType(snapshot.getType()); | ||
| vo.setVolumeUuid(snapshot.getVolumeUuid()); | ||
| vo.setFormat(snapshot.getFormat()); | ||
| vo.setUuid(snapshot.getUuid()); | ||
| vo.setStatus(VolumeSnapshotStatus.valueOf(snapshot.getStatus())); | ||
| vo.setLatest(snapshot.isLatest()); | ||
| vo.setSize(snapshot.getSize()); | ||
| vo.setVolumeType(snapshot.getVolumeType()); | ||
| vo.setTreeUuid(snapshot.getTreeUuid()); | ||
| vo.setDistance(snapshot.getDistance()); | ||
| dbf.persist(vo); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| // 快照组 | ||
| List<VolumeSnapshotGroupVO> newGroups = new ArrayList<>(); | ||
| vmMetadata.volumeSnapshotGroupVO.forEach(group -> { | ||
| VolumeSnapshotGroupVO vo = JSONObjectUtil.toObject(group, VolumeSnapshotGroupVO.class); | ||
| vo.setAccountUuid(msg.getSession().getAccountUuid()); | ||
| newGroups.add(vo); | ||
| }); | ||
| dbf.persistCollection(newGroups); | ||
|
|
||
| // 快照组ref | ||
| List<VolumeSnapshotGroupRefVO> newGroupRefs = new ArrayList<>(); | ||
| vmMetadata.volumeSnapshotGroupRefVO.forEach(group -> { | ||
| VolumeSnapshotGroupRefVO vo = JSONObjectUtil.toObject(group, VolumeSnapshotGroupRefVO.class); | ||
| newGroupRefs.add(vo); | ||
| }); | ||
| dbf.persistCollection(newGroupRefs); | ||
|
|
||
| trigger.next(); | ||
| } | ||
| }); | ||
|
|
||
| flow(new NoRollbackFlow() { | ||
| String __name__ = "register-vmInstance"; | ||
|
|
||
| @Override | ||
| public void run(FlowTrigger trigger, Map data) { | ||
| VmInstanceVO metaVm = JSONObjectUtil.toObject(vmMetadata.vmInstanceVO, VmInstanceVO.class); | ||
| VmInstanceVO newVm = new VmInstanceVO(); | ||
|
|
||
| newVm.setClusterUuid(msg.getClusterUuid()); | ||
| newVm.setZoneUuid(msg.getZoneUuid()); | ||
| newVm.setHostUuid(msg.getHostUuid()); | ||
| // 寻找有没有cache的tag lv 构建imageCache | ||
| // newVm.setImageUuid(); | ||
|
|
||
| newVm.setUuid(metaVm.getUuid()); | ||
| newVm.setName(metaVm.getName()); | ||
| newVm.setDescription(metaVm.getDescription()); | ||
| newVm.setType(metaVm.getType()); | ||
| newVm.setHypervisorType(metaVm.getHypervisorType()); | ||
| newVm.setCreateDate(metaVm.getCreateDate()); | ||
| newVm.setLastOpDate(metaVm.getLastOpDate()); | ||
| newVm.setState(metaVm.getState()); | ||
| newVm.setRootVolumeUuid(metaVm.getRootVolumeUuid()); | ||
| newVm.setInternalId(metaVm.getInternalId()); | ||
| newVm.setCpuNum(metaVm.getCpuNum()); | ||
| newVm.setCpuSpeed(metaVm.getCpuSpeed()); | ||
| newVm.setMemorySize(metaVm.getMemorySize()); | ||
| newVm.setReservedMemorySize(metaVm.getReservedMemorySize()); | ||
| newVm.setAllocatorStrategy(metaVm.getAllocatorStrategy()); | ||
| newVm.setPlatform(metaVm.getPlatform()); | ||
| newVm.setArchitecture(metaVm.getArchitecture()); | ||
| newVm.setGuestOsType(metaVm.getGuestOsType()); | ||
| dbf.persist(newVm); | ||
| vmInstanceInventory = VmInstanceInventory.valueOf(newVm); | ||
| trigger.next(); | ||
| // List<String> vmSystemTags = vmMetadata.vmSystemTags; | ||
| // List<String> vmResourceConfigs = vmMetadata.vmResourceConfigs; | ||
| // | ||
| // try { | ||
| // List<SystemTag> systemTags = getResourceSystemTagFromSystem(VmInstanceVO.class.getSimpleName()); | ||
| // List<GlobalConfig> resourceConfigs = getResourceConfigFromSystem(VmInstanceVO.class.getSimpleName()); | ||
| // | ||
| // List<SystemTagVO> tagVOS = new ArrayList<>(); | ||
| // vmSystemTags.forEach(tag -> { | ||
| // List<String> info = asList(tag.split("_")); | ||
| // String t = info.get(0); | ||
| // Boolean inherent = Boolean.valueOf(info.get(1)); | ||
| // String type = info.get(2); | ||
| // systemTags.forEach(it -> { | ||
| // if (!it.isMatch(t)) { | ||
| // return; | ||
| // } | ||
| // SystemTagVO vo = new SystemTagVO(); | ||
| // vo.setTag(t); | ||
| // vo.setType(TagType.valueOf(type)); | ||
| // vo.setInherent(inherent); | ||
| // vo.setResourceType(VmInstanceVO.class.getSimpleName()); | ||
| // vo.setResourceUuid(newVm.getUuid()); | ||
| // tagVOS.add(vo); | ||
| // }); | ||
| // }); | ||
| // | ||
| // List<ResourceConfigVO> configVOS = new ArrayList<>(); | ||
| // vmResourceConfigs.forEach(tag -> { | ||
| // List<String> info = asList(tag.split("_")); | ||
| // String identity = info.get(0); | ||
| // String value = info.get(1); | ||
| // resourceConfigs.forEach(it -> { | ||
| // if (it.getIdentity() == identity) { | ||
| // return; | ||
| // } | ||
| // ResourceConfigVO vo = new ResourceConfigVO(); | ||
| // vo.setCategory(identity); | ||
| // vo.setName(identity); | ||
| // vo.setValue(value); | ||
| // vo.setResourceType(VmInstanceVO.class.getSimpleName()); | ||
| // vo.setResourceUuid(newVm.getUuid()); | ||
| // configVOS.add(vo); | ||
| // }); | ||
| // }); | ||
| // } catch (IllegalAccessException | InstantiationException e) { | ||
| // throw new RuntimeException(e); | ||
| // } | ||
| } | ||
| }); | ||
|
|
||
| done(new FlowDoneHandler(completion) { | ||
| @Override | ||
| public void handle(Map data) { | ||
| completion.success(vmInstanceInventory); | ||
| } | ||
| }); | ||
|
|
||
| error(new FlowErrorHandler(msg) { | ||
| @Override | ||
| public void handle(ErrorCode errCode, Map data) { | ||
| completion.fail(errCode); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
注册流程缺少回滚会留下脏数据。
当前全是 NoRollbackFlow,任一步失败都会留下已持久化的 Volume/Snapshot/VM/Group 记录。建议增加可回滚 flow / 补偿删除,或统一事务回滚。
🤖 Prompt for AI Agents
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1870 - 2134, The registerVmInstance flow chain currently uses only
NoRollbackFlow steps (read-metadata, register-volume, register-snapshot,
register-vmInstance) and persists entities via dbf.persist/dbf.persistCollection
without compensation, leaving stale
VolumeVO/VolumeSnapshotVO/VolumeSnapshotTreeVO/VolumeSnapshotGroupVO/VolumeSnapshotGroupRefVO/VmInstanceVO
rows on failure; change the persisting flows to support rollback by either (a)
switch those NoRollbackFlow instances to Flow and implement rollback handlers
that delete any entities created in that flow (track created VO UUIDs in
flow-local lists) or (b) wrap the whole chain in a single
transactional/compensating flow that deletes all created entities on error (use
the collected created lists from register-volume, register-snapshot,
register-vmInstance and remove via dbf.remove or appropriate DAO methods in the
Flow.rollback), ensuring completion/error handlers remain unchanged so
completion.fail is called on error.
be2f06d to
47a2c23
Compare
There was a problem hiding this comment.
Actionable comments posted: 20
🤖 Fix all issues with AI agents
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`:
- Around line 19-26: The code can NPE when VmMetadataDTO.getSchemaVersion()
returns null; in upgradeToCurrentVersion(VmMetadataDTO) ensure inputVersion is
null-safe by checking if input.getSchemaVersion() == null and treating it as an
older/default version (e.g., set inputVersion to a safe default like "v0" or
handle it as "older than CURRENT_VERSION") before any
CURRENT_VERSION.equals(...) or compareVersion(...) calls; alternatively make
compareVersion(String, String) null-safe (handle null/empty first) so calls from
upgradeToCurrentVersion and any other callers cannot throw NPEs.
- Around line 1-4: Remove the duplicate/incorrect package declaration and make
the file use the single correct package declaration "com.zstack.core.vmmetadata"
in MetadataVersionConverter.java; delete the "package
org.zstack.compute.vm.metadata;" line, ensure the remaining package line is
"package com.zstack.core.vmmetadata;", and then update any imports/usages across
the codebase that reference org.zstack.compute.vm.metadata to the new package
com.zstack.core.vmmetadata so compilation succeeds.
In `@compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java`:
- Around line 3-4: ResourceConfigDTO is empty but code calls setConfigKey,
setConfigValue, setIsSensitive from VmMetadataConverter.convertResourceConfig;
add private fields configKey (String), configValue (String) and isSensitive
(boolean) to ResourceConfigDTO and implement corresponding getters and setters
(getConfigKey/setConfigKey, getConfigValue/setConfigValue,
isSensitive/setIsSensitive) so the converter can compile and populate these
values.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java`:
- Around line 30-32: The isSensitiveField method currently calls
SENSITIVE_PATTERN.matcher(key) without guarding against null and can throw NPE;
update isSensitiveField(String key) to check for null (or empty) first and
return false (or appropriate non-sensitive default) when key is null before
calling SENSITIVE_PATTERN.matcher(key). Refer to the isSensitiveField method and
SENSITIVE_PATTERN constant to locate the change and add the null check as the
first statement in the method.
- Around line 18-25: SENSITIVE_TAGS is declared but never initialized which
causes a NullPointerException in the static initializer that builds
SENSITIVE_PATTERN; initialize SENSITIVE_TAGS (e.g., as an immutable/empty List
or populated from config) before the static block and guard the regex
construction in the static initializer so it handles an empty list (build a safe
regex or skip Pattern.compile if no tags). Update the static block referencing
SENSITIVE_TAGS and SENSITIVE_PATTERN in SensitiveFieldHandler to use the
initialized list or a fallback to avoid NPEs.
- Around line 3-8: SensitiveFieldHandler is using several types/constants that
are not imported; add import declarations for GlobalConfig,
VM_METADATA_SENSITIVE_KEY, AESCipher, AlertManager, and SystemTagDTO at the top
of SensitiveFieldHandler.java (or the correct packages in your project) so the
compiler can resolve those symbols used in methods of SensitiveFieldHandler and
any references to the VM_METADATA_SENSITIVE_KEY constant, AESCipher encryption
calls, AlertManager alerts, and SystemTagDTO handling.
In `@compute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.java`:
- Around line 3-4: SystemTagDTO is empty but other classes call its
getters/setters; add private fields String tagKey, String tagValue and boolean
isSensitive, and implement the matching public accessors used elsewhere:
getTagKey(), setTagKey(String), getTagValue(), setTagValue(String), and
setIsSensitive(boolean) plus a boolean getter (getIsSensitive() or
isSensitive()) so calls from VmMetadataConverter.convertSystemTag() and
SensitiveFieldHandler compile; keep standard Java bean naming and simple
field-backed implementations inside class SystemTagDTO.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java`:
- Around line 27-139: The convert(...) helpers currently call stream() or
getSnapshotRefs() on input lists/fields that may be null, causing NPEs; update
convert, convertVmNic, convertVolume, convertSnapshot, convertSnapshotGroup (and
any other converter methods) to defensively treat null lists by using
Collections.emptyList() (or Optional.ofNullable(...).orElse(...)) before
streaming, and in convertSnapshotGroup ensure vo.getSnapshotRefs() is
null-checked/empty-defaulted before mapping
VolumeSnapshotGroupRefVO::getSnapshotUuid so snapshotUuidList is an empty list
instead of throwing.
- Around line 1-4: Remove the duplicate package declaration by deleting "package
com.zstack.core.vmmetadata;" so VmMetadataConverter has only the correct
package; add missing imports for VolumeSnapshotGroupRefVO,
VolumeSnapshotReferenceVO, and VolumeSnapshotReferenceTreeVO (use their correct
fully-qualified class names) and remove or replace any references to the
non-existent CoreMemorySnapshotConfigs class (either import the correct
replacement or delete related logic) in the VmMetadataConverter class; and add
null checks before any .stream() calls in the conversion methods (e.g.,
convertVmNic, convertVolume, convertSnapshot and similar converters) to return
an empty collection or handle null input safely to avoid NullPointerException.
In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.java`:
- Around line 7-42: VmMetadataDTO currently lacks getter/setter methods and a
serialVersionUID, causing compilation and deserialization failures; add a
private static final long serialVersionUID and implement public JavaBean getters
and setters for every private field declared in VmMetadataDTO (schemaVersion,
createTime, name, description, hypervisorType, guestOsType, architecture,
platform, type, rootVolumeUuid, imageUuid, memorySize, reservedMemorySize,
cpuNum, cpuSpeed, volumeList, nicList, snapshotList, snapshotGroupList,
systemTags, resourceConfigs). Also ensure the DTO exposes the exact accessor
names expected by callers (e.g., provide methods matching usages in
VmMetadataConverter/VmMetadataSerializer such as setVmUuid/getVmUuid and
setNumCpus/getNumCpus or add delegating methods that map to the existing fields)
so JSONObjectUtil and converters can bind properties correctly.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java`:
- Around line 76-81: The loop in VmMetadataSerializer that decrypts sensitive
fields calls dto.getResourceConfigs().forEach(...) without null-check, causing
NPE; before iterating, guard the call by checking dto.getResourceConfigs() !=
null (or use Optional/Collections.emptyList()) so you only call forEach when the
list is non-null, and keep using SensitiveFieldHandler.isSensitiveField(...) and
SensitiveFieldHandler.decrypt(...) inside the guarded block.
- Around line 62-71: In VmMetadataSerializer, the comparison
storedChecksum.equals(calculatedChecksum) can NPE if storedChecksum is null;
change the check to first handle null (e.g., treat null as mismatch) by
replacing the equality test with a null-safe check (for example: if
(storedChecksum == null || !storedChecksum.equals(calculatedChecksum)) ) so that
getSha256Checksum() being null triggers the same AlertManager.alert and
SecurityException path; reference storedChecksum, getSha256Checksum(),
calculatedChecksum, Sha256Utils.hash and JSONObjectUtil.toJsonString to locate
the code to update.
- Around line 22-31: VmMetadataSerializer.serialize currently calls
dto.getSystemTags() and dto.getResourceConfigs() without null checks which can
cause NPEs; update serialize to null-check VmMetadataDTO.getSystemTags() before
passing to SensitiveFieldHandler.filterSensitiveTags and only call
dto.setSystemTags(...) when non-null (or set an empty list if desired), and
null-check VmMetadataDTO.getResourceConfigs() before iterating: if non-null,
iterate and for each config check
SensitiveFieldHandler.isSensitiveField(config.getConfigKey()) then set
config.setConfigValue(SensitiveFieldHandler.encrypt(config.getConfigValue()));
ensure all accessors (getSystemTags, getResourceConfigs, config.getConfigKey,
config.getConfigValue) are guarded so no method is invoked on null.
- Around line 3-6: The imports in VmMetadataSerializer use the wrong base
packages; replace com.zstack.* imports with the project's canonical packages so
the class imports GlobalConfig from org.zstack.core.config (use
org.zstack.core.config.GlobalConfig), AlertManager from org.zstack.core.alert
(org.zstack.core.alert.AlertManager), and the JSON/codec utilities from
org.zstack.utils (org.zstack.utils.json.JSONObjectUtil and
org.zstack.utils.codec.Sha256Utils) to match project conventions.
In `@compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java`:
- Around line 47-53: The class VolumeMetadataDTO is missing the String field
referenced by getPrimaryStorageUuid() and setPrimaryStorageUuid(); add a private
static String primaryStorageUuid declaration (matching the static access in
those methods) alongside the other DTO fields in VolumeMetadataDTO so the getter
and setter compile correctly, and ensure any intended visibility (private) and
default initialization are consistent with the other fields.
- Around line 6-26: All fields in VolumeMetadataDTO (e.g., description, name,
vmInstanceUuid, rootImageUuid, diskOfferingUuid, format, installPath, type,
size, actualSize, deviceId, state, status, createDate, lastOpDate,
lastDetachDate, lastAttachDate, lastVmInstanceUuid, isShareable, volumeQos,
protocol) are incorrectly declared static; change each to an instance field
(remove the static modifier) and convert any static getter/setter methods to
instance methods on VolumeMetadataDTO so each DTO holds independent state; after
that, search for call sites that reference class-level access
(VolumeMetadataDTO.field or VolumeMetadataDTO.getX()) and update them to use an
instance (dto.getX()/dto.setX()) or adjust construction, and run tests to ensure
no serialization/ORM annotations rely on static members.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java`:
- Around line 9489-9499: The code in VmInstanceBase contains a residual
DTO/serializer block referencing undefined symbols (vmUuid, volumeVos,
snapshotVos, groupVos, tagVos, configVos, snapshotDto) and won't compile; either
remove the entire DTO/serializer/supplementSnapshotReference lines (the calls to
VmMetadataConverter.convert, VmMetadataSerializer.serialize and
VmMetadataConverter.supplementSnapshotReference) if metadata serialization is no
longer needed, or properly obtain and declare the missing variables before use
(ensure vmUuid is set to the current VM UUID, populate
volumeVos/snapshotVos/groupVos/tagVos/configVos from the DB or method context,
and get snapshotDto from the snapshot handling code) so the calls in
VmInstanceBase compile and have valid inputs.
In `@header/src/main/java/org/zstack/header/vm/VmMetadata.java`:
- Around line 33-36: The two list fields in VmMetadata—volumeSnapshotGroupVO and
volumeSnapshotGroupRefVO—are named in singular form while their type is List;
rename them to plural (e.g., volumeSnapshotGroupsVO and
volumeSnapshotGroupRefsVO or volumeSnapshotGroupVOs and
volumeSnapshotGroupRefVOs) and update all references/usages (constructors,
getters/setters, JSON/mapping annotations, code that reads/writes these fields)
to use the new names to keep naming consistent with their List semantics; ensure
any serialization names or API contracts are adjusted if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3814-3824: The CloudBusCallBack currently constructs an
UpdateVmInstanceMetadataOnPrimaryStorageReply but never invokes the provided
ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion,
so the caller is never notified; inside the CloudBusCallBack.run(MessageReply r)
use completion.success(reply) when r.isSuccess() and completion.fail(...) with
the same Platform-operr-created error when !r.isSuccess(), and then still send
bus.reply(msg, reply) if needed; ensure you reference the existing
UpdateVmInstanceMetadataOnPrimaryStorageReply, CloudBusCallBack, completion
parameter and the current error construction
(Platform.operr(...).withCause(r.getError())) so the completion and bus.reply
behavior are consistent and the caller receives the finish notification.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1886-1891: The error message in run(MessageReply r) incorrectly
logs self.getUuid() (PrimaryStorageBase) instead of the VM's UUID; update the
operr call in the failure branch to reference the VM's UUID from the surrounding
VM context (e.g., use the vm instance or VM inventory variable such as
vm.getUuid() or vmInventory.getUuid(), or extract it from VmInstanceSpec/VmSpec
if those are in scope) so the error message correctly identifies the VM; ensure
the chosen VM UUID variable is in scope and compile-clean.
🧹 Nitpick comments (9)
header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java (1)
3-9: 接口命名不当:使用了 "Exception" 后缀但并非异常类。根据编码规范,
Exception后缀应保留给实际的异常类(继承自Exception或Throwable)。当前RegisterVmInstanceException是一个普通接口,定义了 VM 注册相关的扩展方法,命名可能造成误解。建议重命名为更能体现其职责的名称,例如:
RegisterVmInstanceExtensionRegisterVmInstanceHandlerRegisterVmInstancePathUpdater♻️ 建议的重命名
-public interface RegisterVmInstanceException { +public interface RegisterVmInstanceExtension { String updateVolumeInstallPath(String installPath); String updateVolumeSnapshotInstallPath(String installPath); PrimaryStorageType getPrimaryStorageType(); }As per coding guidelines: "异常类应以
Exception结尾",此规则隐含了Exception后缀应仅用于实际的异常类。compute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupMetadataDTO.java (1)
7-13: 字段可见性不一致
refList字段使用包级私有访问(无修饰符),而其他字段都是public。建议保持一致性。♻️ 建议统一字段可见性
public Timestamp createDate; public Timestamp lastOpDate; - List<SnapshotGroupRefMetadataDTO> refList; + public List<SnapshotGroupRefMetadataDTO> refList;或者,如果需要封装,将所有字段改为 private 并添加 getter/setter。
header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java (1)
22-25:__example__()方法应填充示例数据以生成更完整的 API 文档。当前实现返回空的 reply 对象,没有设置
vmInstanceMetadata的示例值。参考其他类似的 Reply 类(如APIRegisterVmInstanceReply),建议添加示例数据以提升 API 文档的可读性。📝 建议的修改
public static APIGetVmInstanceMetadataFromPrimaryStorageReply __example__() { APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply(); + reply.setVmInstanceMetadata(java.util.Arrays.asList("{\"schemaVersion\":\"v1.0\",\"name\":\"example-vm\"}")); return reply; }header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java (1)
15-16: 建议:字段名uuid语义不够明确,可考虑改为primaryStorageUuid。当前类名
APIGetVmInstanceMetadataFromPrimaryStorageMsg同时涉及 VmInstance 和 PrimaryStorage,字段名仅用uuid容易产生歧义。参考同 PR 中APIRegisterVmInstanceMsg.java使用primaryStorageUuid命名,更清晰地表达字段用途。♻️ 建议重命名
public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements PrimaryStorageMessage { `@APIParam`(resourceType = PrimaryStorageVO.class) - private String uuid; + private String primaryStorageUuid; - public String getUuid() { - return uuid; + public String getPrimaryStorageUuid() { + return primaryStorageUuid; } - public void setUuid(String uuid) { - this.uuid = uuid; + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; } `@Override` - public String getPrimaryStorageUuid() { - return uuid; + public String getPrimaryStorageUuid() { + return primaryStorageUuid; } public static APIGetVmInstanceMetadataFromPrimaryStorageMsg __example__() { APIGetVmInstanceMetadataFromPrimaryStorageMsg msg = new APIGetVmInstanceMetadataFromPrimaryStorageMsg(); - msg.setUuid(uuid()); + msg.setPrimaryStorageUuid(uuid()); return msg; } }compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java (1)
87-99:compareVersion缺少异常处理。
Integer.parseInt可能因非数字版本号抛出NumberFormatException,建议添加防御性处理。♻️ 建议改进
private static int compareVersion(String version1, String version2) { + if (version1 == null || version2 == null) { + throw new IllegalArgumentException("版本号不能为空"); + } String[] v1 = version1.replace("v", "").split("\\."); String[] v2 = version2.replace("v", "").split("\\."); int len = Math.max(v1.length, v2.length); for (int i = 0; i < len; i++) { - int num1 = i < v1.length ? Integer.parseInt(v1[i]) : 0; - int num2 = i < v2.length ? Integer.parseInt(v2[i]) : 0; + int num1 = 0; + int num2 = 0; + try { + num1 = i < v1.length ? Integer.parseInt(v1[i]) : 0; + num2 = i < v2.length ? Integer.parseInt(v2[i]) : 0; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("版本号格式错误: " + version1 + " 或 " + version2, e); + } if (num1 != num2) { return num1 - num2; } } return 0; }storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (2)
1867-1867: 建议使用FlowChainBuilder工厂方法。项目中其他地方(如 Line 1260)使用
FlowChainBuilder.newShareFlowChain()创建 ShareFlowChain,保持一致性有助于代码可维护性。♻️ 建议修改
- FlowChain chain = new ShareFlowChain(); + FlowChain chain = FlowChainBuilder.newShareFlowChain();
2133-2152: 使用已弃用的newInstance()方法且效率较低。
clazz.newInstance()在 Java 9+ 已被弃用,应使用clazz.getDeclaredConstructor().newInstance()。- 每个字段都创建新实例效率低下,应在循环外创建一次。
♻️ 建议改进
private List<SystemTag> getResourceSystemTagFromSystem(String resourceType) throws IllegalAccessException, InstantiationException { List<SystemTag> systemTags = new ArrayList<>(); Set<Class<?>> classes = BeanUtils.reflections.getTypesAnnotatedWith(TagDefinition.class); for (Class clazz : classes) { + Object instance = null; + try { + instance = clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + continue; + } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (!SystemTag.class.isAssignableFrom(field.getType())) { continue; } - SystemTag systemTag = (SystemTag) field.get(clazz.newInstance()); + SystemTag systemTag = (SystemTag) field.get(instance); if (resourceType.equals(systemTag.getResourceClass().getName())) { systemTags.add(systemTag); } } } return systemTags; }sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java (2)
5-5: 冗余的导入声明。
import org.zstack.sdk.*是多余的,因为当前类已经在org.zstack.sdk包中。♻️ 建议修改
import java.util.HashMap; import java.util.Map; -import org.zstack.sdk.*;
43-47: 建议使用泛型参数类型。
systemTags和userTags使用原始List类型,建议使用List<String>以提高类型安全性。♻️ 建议修改
`@Param`(required = false) - public java.util.List systemTags; + public java.util.List<String> systemTags; `@Param`(required = false) - public java.util.List userTags; + public java.util.List<String> userTags;
| package org.zstack.compute.vm.metadata; | ||
|
|
||
| package com.zstack.core.vmmetadata; | ||
|
|
There was a problem hiding this comment.
重复的包声明导致编译错误。
文件包含两个包声明(Line 1 和 Line 3),这会导致编译失败。
🐛 建议修复
package org.zstack.compute.vm.metadata;
-package com.zstack.core.vmmetadata;
-
import com.zstack.core.GlobalConfig;同时需要修正导入路径:
-import com.zstack.core.GlobalConfig;
-import com.zstack.utils.AssertUtils;
+import org.zstack.core.config.GlobalConfig;
+import org.zstack.utils.AssertUtils;🤖 Prompt for AI Agents
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`
around lines 1 - 4, Remove the duplicate/incorrect package declaration and make
the file use the single correct package declaration "com.zstack.core.vmmetadata"
in MetadataVersionConverter.java; delete the "package
org.zstack.compute.vm.metadata;" line, ensure the remaining package line is
"package com.zstack.core.vmmetadata;", and then update any imports/usages across
the codebase that reference org.zstack.compute.vm.metadata to the new package
com.zstack.core.vmmetadata so compilation succeeds.
| public static VmMetadataDTO upgradeToCurrentVersion(VmMetadataDTO input) { | ||
| AssertUtils.notNull(input, "VmMetadataDTO cannot be null"); | ||
| String inputVersion = input.getSchemaVersion(); | ||
|
|
||
| // 版本相同,无需升级 | ||
| if (CURRENT_VERSION.equals(inputVersion)) { | ||
| return input; | ||
| } |
There was a problem hiding this comment.
inputVersion 可能为 null,导致 NPE。
如果 DTO 的 schemaVersion 未设置,input.getSchemaVersion() 返回 null,后续 CURRENT_VERSION.equals(inputVersion) 虽然安全,但 compareVersion(inputVersion, CURRENT_VERSION) 在 Line 88 会因 version1.replace("v", "") 抛出 NPE。
🛡️ 建议修复
public static VmMetadataDTO upgradeToCurrentVersion(VmMetadataDTO input) {
AssertUtils.notNull(input, "VmMetadataDTO cannot be null");
String inputVersion = input.getSchemaVersion();
+ // 版本为空,视为最旧版本需要升级
+ if (inputVersion == null) {
+ inputVersion = "v1.0";
+ }
+
// 版本相同,无需升级
if (CURRENT_VERSION.equals(inputVersion)) {
return input;
}📝 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.
| public static VmMetadataDTO upgradeToCurrentVersion(VmMetadataDTO input) { | |
| AssertUtils.notNull(input, "VmMetadataDTO cannot be null"); | |
| String inputVersion = input.getSchemaVersion(); | |
| // 版本相同,无需升级 | |
| if (CURRENT_VERSION.equals(inputVersion)) { | |
| return input; | |
| } | |
| public static VmMetadataDTO upgradeToCurrentVersion(VmMetadataDTO input) { | |
| AssertUtils.notNull(input, "VmMetadataDTO cannot be null"); | |
| String inputVersion = input.getSchemaVersion(); | |
| // 版本为空,视为最旧版本需要升级 | |
| if (inputVersion == null) { | |
| inputVersion = "v1.0"; | |
| } | |
| // 版本相同,无需升级 | |
| if (CURRENT_VERSION.equals(inputVersion)) { | |
| return input; | |
| } |
🤖 Prompt for AI Agents
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`
around lines 19 - 26, The code can NPE when VmMetadataDTO.getSchemaVersion()
returns null; in upgradeToCurrentVersion(VmMetadataDTO) ensure inputVersion is
null-safe by checking if input.getSchemaVersion() == null and treating it as an
older/default version (e.g., set inputVersion to a safe default like "v0" or
handle it as "older than CURRENT_VERSION") before any
CURRENT_VERSION.equals(...) or compareVersion(...) calls; alternatively make
compareVersion(String, String) null-safe (handle null/empty first) so calls from
upgradeToCurrentVersion and any other callers cannot throw NPEs.
| public class ResourceConfigDTO { | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ResourceConfigDTO usage and related context
echo "=== ResourceConfigDTO Usage ==="
rg -n "ResourceConfigDTO" --type java -B 2 -A 2
echo -e "\n=== Checking VmMetadataDTO for context ==="
fd "VmMetadataDTO.java" --type fRepository: MatheMatrix/zstack
Length of output: 3676
🏁 Script executed:
#!/bin/bash
# Check the complete convertResourceConfig method
echo "=== Complete ResourceConfigDTO conversion method ==="
sed -n '170,180p' compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java
# Also check if there are any other usages
echo -e "\n=== All lines using ResourceConfigDTO methods ==="
rg "ResourceConfigDTO" --type java -A 5 | grep -E "(setConfig|getConfig|ResourceConfigDTO\()"Repository: MatheMatrix/zstack
Length of output: 1169
🏁 Script executed:
#!/bin/bash
# Read the complete ResourceConfigDTO file
echo "=== Complete ResourceConfigDTO.java content ==="
cat -n compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java
# Also check if there's a getter/setter pattern or fields we're missing
echo -e "\n=== Check for any related backup or alternative files ==="
find compute/src -name "*ResourceConfig*" -type fRepository: MatheMatrix/zstack
Length of output: 717
ResourceConfigDTO 类缺少必要的字段和方法实现
ResourceConfigDTO 虽然被声明为空类,但在 VmMetadataConverter.convertResourceConfig() 方法中(第172-178行)已调用以下方法:
setConfigKey()setConfigValue()setIsSensitive()
这些方法在类中并不存在,会导致编译或运行时错误。需要补充以下字段及对应的 getter/setter 方法:
configKey(配置键)configValue(配置值)isSensitive(是否敏感字段)
🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java`
around lines 3 - 4, ResourceConfigDTO is empty but code calls setConfigKey,
setConfigValue, setIsSensitive from VmMetadataConverter.convertResourceConfig;
add private fields configKey (String), configValue (String) and isSensitive
(boolean) to ResourceConfigDTO and implement corresponding getters and setters
(getConfigKey/setConfigKey, getConfigValue/setConfigValue,
isSensitive/setIsSensitive) so the converter can compile and populate these
values.
|
|
||
| import org.apache.commons.lang.StringUtils; | ||
| import org.zstack.utils.CollectionUtils; | ||
|
|
||
| import java.util.List; | ||
| import java.util.regex.Pattern; |
There was a problem hiding this comment.
缺少关键的导入声明。
以下类/常量被使用但未导入:
GlobalConfigVM_METADATA_SENSITIVE_KEYAESCipherAlertManagerSystemTagDTO
🐛 建议添加导入
package org.zstack.compute.vm.metadata;
+import org.zstack.core.config.GlobalConfig;
+import org.zstack.core.alert.AlertManager;
+import org.zstack.utils.cipher.AESCipher;
+import static org.zstack.compute.vm.VmGlobalConfig.VM_METADATA_SENSITIVE_KEY;
import org.apache.commons.lang.StringUtils;
import org.zstack.utils.CollectionUtils;
import java.util.List;
import java.util.regex.Pattern;📝 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.
| import org.apache.commons.lang.StringUtils; | |
| import org.zstack.utils.CollectionUtils; | |
| import java.util.List; | |
| import java.util.regex.Pattern; | |
| package org.zstack.compute.vm.metadata; | |
| import org.zstack.core.config.GlobalConfig; | |
| import org.zstack.core.alert.AlertManager; | |
| import org.zstack.utils.cipher.AESCipher; | |
| import static org.zstack.compute.vm.VmGlobalConfig.VM_METADATA_SENSITIVE_KEY; | |
| import org.apache.commons.lang.StringUtils; | |
| import org.zstack.utils.CollectionUtils; | |
| import java.util.List; | |
| import java.util.regex.Pattern; |
🤖 Prompt for AI Agents
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java`
around lines 3 - 8, SensitiveFieldHandler is using several types/constants that
are not imported; add import declarations for GlobalConfig,
VM_METADATA_SENSITIVE_KEY, AESCipher, AlertManager, and SystemTagDTO at the top
of SensitiveFieldHandler.java (or the correct packages in your project) so the
compiler can resolve those symbols used in methods of SensitiveFieldHandler and
any references to the VM_METADATA_SENSITIVE_KEY constant, AESCipher encryption
calls, AlertManager alerts, and SystemTagDTO handling.
| // 敏感标签列表 | ||
| private static final List<String> SENSITIVE_TAGS ; | ||
|
|
||
| static { | ||
| // 初始化敏感关键词正则 | ||
| String regex = String.join("|", SENSITIVE_TAGS); | ||
| SENSITIVE_PATTERN = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); | ||
| } |
There was a problem hiding this comment.
SENSITIVE_TAGS 未初始化,会导致静态初始化块 NPE。
SENSITIVE_TAGS 在 Line 19 声明但未赋值,Line 23 中 String.join("|", SENSITIVE_TAGS) 会抛出 NullPointerException。
🐛 建议修复
// 敏感标签列表
- private static final List<String> SENSITIVE_TAGS ;
+ private static final List<String> SENSITIVE_TAGS = Arrays.asList(
+ "password", "secret", "token", "credential", "apikey"
+ // 根据实际需求添加更多敏感关键词
+ );📝 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.
| // 敏感标签列表 | |
| private static final List<String> SENSITIVE_TAGS ; | |
| static { | |
| // 初始化敏感关键词正则 | |
| String regex = String.join("|", SENSITIVE_TAGS); | |
| SENSITIVE_PATTERN = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); | |
| } | |
| // 敏感标签列表 | |
| private static final List<String> SENSITIVE_TAGS = Arrays.asList( | |
| "password", "secret", "token", "credential", "apikey" | |
| // 根据实际需求添加更多敏感关键词 | |
| ); | |
| static { | |
| // 初始化敏感关键词正则 | |
| String regex = String.join("|", SENSITIVE_TAGS); | |
| SENSITIVE_PATTERN = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); | |
| } |
🤖 Prompt for AI Agents
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java`
around lines 18 - 25, SENSITIVE_TAGS is declared but never initialized which
causes a NullPointerException in the static initializer that builds
SENSITIVE_PATTERN; initialize SENSITIVE_TAGS (e.g., as an immutable/empty List
or populated from config) before the static block and guard the regex
construction in the static initializer so it handles an empty list (build a safe
regex or skip Pattern.compile if no tags). Update the static block referencing
SENSITIVE_TAGS and SENSITIVE_PATTERN in SensitiveFieldHandler to use the
initialized list or a fallback to avoid NPEs.
| public static String getPrimaryStorageUuid() { | ||
| return primaryStorageUuid; | ||
| } | ||
|
|
||
| public static void setPrimaryStorageUuid(String primaryStorageUuid) { | ||
| VolumeMetadataDTO.primaryStorageUuid = primaryStorageUuid; | ||
| } |
There was a problem hiding this comment.
编译错误:引用了未声明的字段 primaryStorageUuid
getPrimaryStorageUuid() 和 setPrimaryStorageUuid() 方法引用了 primaryStorageUuid 字段,但该字段未在类中声明(Lines 6-26 的字段列表中缺失)。这将导致编译失败。
🐛 建议修复:添加缺失的字段声明
public class VolumeMetadataDTO {
- public static String description;
+ private String primaryStorageUuid;
+ private String description;
// ... other fields🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java`
around lines 47 - 53, The class VolumeMetadataDTO is missing the String field
referenced by getPrimaryStorageUuid() and setPrimaryStorageUuid(); add a private
static String primaryStorageUuid declaration (matching the static access in
those methods) alongside the other DTO fields in VolumeMetadataDTO so the getter
and setter compile correctly, and ensure any intended visibility (private) and
default initialization are consistent with the other fields.
|
|
||
| VmInstanceVO vmVo = dbf.findByUuid(vmUuid, VmInstanceVO.class); | ||
| List<VmNicVO> nicVos = dbf.listBySql("select * from VmNicVO where vmInstanceUuid = ?", VmNicVO.class, vmUuid); | ||
|
|
||
| VmMetadataDTO vmDto = VmMetadataConverter.convert(vmVo, nicVos, volumeVos, snapshotVos, groupVos, tagVos, configVos); | ||
|
|
||
| String json = VmMetadataSerializer.serialize(vmDto); | ||
|
|
||
| // | ||
| VolumeSnapshotReferenceVO refVo = dbf.findByUuid(snapshotDto.getSnapshotUuid(), VolumeSnapshotReferenceVO.class); | ||
| VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo); |
There was a problem hiding this comment.
存在未定义变量与残留逻辑,当前代码无法编译。
vmUuid、volumeVos、snapshotVos、groupVos、tagVos、configVos、snapshotDto 均未定义,导致编译失败。若确实要走 DTO/Serializer,请完整补齐变量来源;否则应删除这段残留代码。
🧹 建议移除未定义变量块(如不再需要 DTO/Serializer)
- VmInstanceVO vmVo = dbf.findByUuid(vmUuid, VmInstanceVO.class);
- List<VmNicVO> nicVos = dbf.listBySql("select * from VmNicVO where vmInstanceUuid = ?", VmNicVO.class, vmUuid);
-
- VmMetadataDTO vmDto = VmMetadataConverter.convert(vmVo, nicVos, volumeVos, snapshotVos, groupVos, tagVos, configVos);
-
- String json = VmMetadataSerializer.serialize(vmDto);
-
- //
- VolumeSnapshotReferenceVO refVo = dbf.findByUuid(snapshotDto.getSnapshotUuid(), VolumeSnapshotReferenceVO.class);
- VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo);📝 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.
| VmInstanceVO vmVo = dbf.findByUuid(vmUuid, VmInstanceVO.class); | |
| List<VmNicVO> nicVos = dbf.listBySql("select * from VmNicVO where vmInstanceUuid = ?", VmNicVO.class, vmUuid); | |
| VmMetadataDTO vmDto = VmMetadataConverter.convert(vmVo, nicVos, volumeVos, snapshotVos, groupVos, tagVos, configVos); | |
| String json = VmMetadataSerializer.serialize(vmDto); | |
| // | |
| VolumeSnapshotReferenceVO refVo = dbf.findByUuid(snapshotDto.getSnapshotUuid(), VolumeSnapshotReferenceVO.class); | |
| VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo); |
🤖 Prompt for AI Agents
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines
9489 - 9499, The code in VmInstanceBase contains a residual DTO/serializer block
referencing undefined symbols (vmUuid, volumeVos, snapshotVos, groupVos, tagVos,
configVos, snapshotDto) and won't compile; either remove the entire
DTO/serializer/supplementSnapshotReference lines (the calls to
VmMetadataConverter.convert, VmMetadataSerializer.serialize and
VmMetadataConverter.supplementSnapshotReference) if metadata serialization is no
longer needed, or properly obtain and declare the missing variables before use
(ensure vmUuid is set to the current VM UUID, populate
volumeVos/snapshotVos/groupVos/tagVos/configVos from the DB or method context,
and get snapshotDto from the snapshot handling code) so the calls in
VmInstanceBase compile and have valid inputs.
| // VolumeSnapshotGroupVO.toString | ||
| public List<String> volumeSnapshotGroupVO = new ArrayList<>(); | ||
| // VolumeSnapshotGroupRefVO.toString | ||
| public List<String> volumeSnapshotGroupRefVO = new ArrayList<>(); |
There was a problem hiding this comment.
列表字段命名建议复数化以匹配类型
volumeSnapshotGroupVO 与 volumeSnapshotGroupRefVO 是 List,但名称为单数,类型语义不清晰,容易误用。建议改为复数形式并同步更新引用。
As per coding guidelines: “命名应尽量用完整的单词组合表达意图,并在名称中体现数据类型或用途(例如在常量与变量名称中,将类型词放在末尾)”。
✅ 建议修改
- public List<String> volumeSnapshotGroupVO = new ArrayList<>();
+ public List<String> volumeSnapshotGroupVOs = new ArrayList<>();
// VolumeSnapshotGroupRefVO.toString
- public List<String> volumeSnapshotGroupRefVO = new ArrayList<>();
+ public List<String> volumeSnapshotGroupRefVOs = new ArrayList<>();📝 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.
| // VolumeSnapshotGroupVO.toString | |
| public List<String> volumeSnapshotGroupVO = new ArrayList<>(); | |
| // VolumeSnapshotGroupRefVO.toString | |
| public List<String> volumeSnapshotGroupRefVO = new ArrayList<>(); | |
| // VolumeSnapshotGroupVO.toString | |
| public List<String> volumeSnapshotGroupVOs = new ArrayList<>(); | |
| // VolumeSnapshotGroupRefVO.toString | |
| public List<String> volumeSnapshotGroupRefVOs = new ArrayList<>(); |
🤖 Prompt for AI Agents
In `@header/src/main/java/org/zstack/header/vm/VmMetadata.java` around lines 33 -
36, The two list fields in VmMetadata—volumeSnapshotGroupVO and
volumeSnapshotGroupRefVO—are named in singular form while their type is List;
rename them to plural (e.g., volumeSnapshotGroupsVO and
volumeSnapshotGroupRefsVO or volumeSnapshotGroupVOs and
volumeSnapshotGroupRefVOs) and update all references/usages (constructors,
getters/setters, JSON/mapping annotations, code that reads/writes these fields)
to use the new names to keep naming consistent with their List semantics; ensure
any serialization names or API contracts are adjusted if needed.
| bus.send(umsg, new CloudBusCallBack(msg) { | ||
| @Override | ||
| public void run(MessageReply r) { | ||
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | ||
| if (!r.isSuccess()) { | ||
| reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) | ||
| .withCause(r.getError())); | ||
| } | ||
| bus.reply(msg, reply); | ||
| } | ||
| }); |
There was a problem hiding this comment.
严重错误:completion 回调从未被调用。
方法签名包含 ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion 参数,但在 CloudBusCallBack 中直接使用了 bus.reply(msg, reply) 而不是调用 completion.success(reply) 或 completion.fail(errorCode)。
这会导致调用方永远无法收到完成通知,可能造成调用方挂起或行为异常。
🐛 建议修复
bus.send(umsg, new CloudBusCallBack(msg) {
`@Override`
public void run(MessageReply r) {
UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
if (!r.isSuccess()) {
- reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid())
- .withCause(r.getError()));
+ completion.fail(Platform.operr("failed to update vm metadata on hypervisor for rootVolume[uuid=%s].", msg.getRootVolumeUuid())
+ .causedBy(r.getError()));
+ return;
}
- bus.reply(msg, reply);
+ completion.success(reply);
}
});🤖 Prompt for AI Agents
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3814 - 3824, The CloudBusCallBack currently constructs an
UpdateVmInstanceMetadataOnPrimaryStorageReply but never invokes the provided
ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion,
so the caller is never notified; inside the CloudBusCallBack.run(MessageReply r)
use completion.success(reply) when r.isSuccess() and completion.fail(...) with
the same Platform-operr-created error when !r.isSuccess(), and then still send
bus.reply(msg, reply) if needed; ensure you reference the existing
UpdateVmInstanceMetadataOnPrimaryStorageReply, CloudBusCallBack, completion
parameter and the current error construction
(Platform.operr(...).withCause(r.getError())) so the completion and bus.reply
behavior are consistent and the caller receives the finish notification.
| public void run(MessageReply r) { | ||
| if (!r.isSuccess()) { | ||
| trigger.fail(operr("failed to update vm[uuid=%s] on hypervisor.", | ||
| self.getUuid()).withCause(r.getError())); | ||
| return; | ||
| } |
There was a problem hiding this comment.
错误消息中的 UUID 引用有误。
Line 1888-1889 的错误消息使用 self.getUuid(),但 self 是 PrimaryStorageBase,这里应该是 VM 元数据读取失败的错误,不应引用主存储 UUID。
🐛 建议修复
`@Override`
public void run(MessageReply r) {
if (!r.isSuccess()) {
- trigger.fail(operr("failed to update vm[uuid=%s] on hypervisor.",
- self.getUuid()).withCause(r.getError()));
+ trigger.fail(operr("failed to read vm metadata from hypervisor[hostUuid=%s], path=%s",
+ msg.getHostUuid(), msg.getMetadataPath()).withCause(r.getError()));
return;
}🤖 Prompt for AI Agents
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1886 - 1891, The error message in run(MessageReply r) incorrectly
logs self.getUuid() (PrimaryStorageBase) instead of the VM's UUID; update the
operr call in the failure branch to reference the VM's UUID from the surrounding
VM context (e.g., use the vm instance or VM inventory variable such as
vm.getUuid() or vmInventory.getUuid(), or extract it from VmInstanceSpec/VmSpec
if those are in scope) so the error message correctly identifies the VM; ensure
the chosen VM UUID variable is in scope and compile-clean.
47a2c23 to
adb1c07
Compare
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java (1)
221-237:⚠️ Potential issue | 🟠 Major删除未使用的
setVmInstanceInfoFromRootDiskAO方法。该方法为
private static,在整个代码库中未被任何地方调用,应作为死代码删除。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java` around lines 221 - 237, Remove the dead private static method setVmInstanceInfoFromRootDiskAO from the VmInstanceUtils class: it is unused across the codebase so delete the entire method body and signature (including references to CreateVmInstanceMsg, APICreateVmInstanceMsg, DiskAO and VmSystemTags inside it) to eliminate dead code and simplify the class.
♻️ Duplicate comments (30)
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (3)
1851-1855:⚠️ Potential issue | 🟠 Major失败分支没有设置 error,调用方会把注册失败误判为成功。
Line [1852] 到 Line [1854] 仅发布事件,未写入
event.setError(errorCode)。🔧 建议修复
`@Override` public void fail(ErrorCode errorCode) { + event.setError(errorCode); bus.publish(event); chain.next(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 1851 - 1855, In PrimaryStorageBase's anonymous fail(ErrorCode) handler, the error is not propagated to the event causing callers to treat failures as success; set the error on the event (call event.setError(errorCode)) before publishing (bus.publish(event)) and then proceed with chain.next() so the failure state is correctly communicated to listeners; locate the fail(ErrorCode) method in PrimaryStorageBase and update the event handling accordingly.
1888-1889:⚠️ Potential issue | 🟡 Minor读取元数据失败日志引用了主存储 UUID,定位信息不准确。
Line [1888] 到 Line [1889] 当前文案是 “update vm...”,且使用
self.getUuid()(主存储 UUID),与真实失败点(host/path)不匹配。🔧 建议修复
- trigger.fail(operr("failed to update vm[uuid=%s] on hypervisor.", - self.getUuid()).withCause(r.getError())); + trigger.fail(operr("failed to read vm metadata from hypervisor[hostUuid=%s], path=%s", + msg.getHostUuid(), msg.getMetadataPath()).withCause(r.getError())); return;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 1888 - 1889, The error message incorrectly says "failed to update vm" and uses self.getUuid() (primary storage UUID); change the failure text to reflect the actual metadata read failure and include the correct host/path identifiers instead of self.getUuid(); locate the trigger.fail(...) call (uses operr(...) and self.getUuid()) and update the message to something like "failed to read metadata on host/path" and substitute self.getUuid() with the host UUID or path variable (e.g., host.getUuid() and/or path) used in this context so the log points to the real failure location.
1875-2127:⚠️ Potential issue | 🟠 Major注册链路全部使用 NoRollbackFlow,任一步失败都会遗留脏数据。
register-volume/register-snapshot/register-vmInstance都在落库,但没有补偿删除或回滚路径,失败后会残留 VO 记录。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 1875 - 2127, The three registration flows ("register-volume", "register-snapshot", "register-vmInstance") persist VO records (VolumeVO, VolumeSnapshotTreeVO, VolumeSnapshotVO, VolumeSnapshotGroupVO, VolumeSnapshotGroupRefVO, VmInstanceVO) using dbf.persist / dbf.persistCollection but are NoRollbackFlow so failures leave DB garbage; convert each NoRollbackFlow to a RollbackFlow (or implement a rollback method) and in the run() record created entity identifiers (e.g. collect newVolumes' UUIDs, created VolumeSnapshotTreeVO/VolumeSnapshotVO UUIDs, and newVm UUID) into the flow's context (or a shared list in the data map), then implement rollback(FlowRollback trigger, Map data) to delete those records via dbf (removeByPrimaryKey or appropriate delete queries) so any partial persistence is cleaned up on failure; ensure you also move dbf.persistCollection/ dbf.persist calls inside the run() where the tracking occurs and reference the flow names ("register-volume", "register-snapshot", "register-vmInstance"), dbf.persistCollection, dbf.persist, VolumeVO, VolumeSnapshotTreeVO, VolumeSnapshotVO, VolumeSnapshotGroupVO, VolumeSnapshotGroupRefVO, and VmInstanceVO when making the changes.compute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.java (1)
3-4:⚠️ Potential issue | 🔴 Critical
SystemTagDTO为空会导致调用方编译失败该类缺少调用方依赖的字段与访问器(如
setTagKey/getTagKey/setTagValue/setIsSensitive)。建议修复
public class SystemTagDTO { + private String tagKey; + private String tagValue; + private boolean isSensitive; + + public String getTagKey() { + return tagKey; + } + + public void setTagKey(String tagKey) { + this.tagKey = tagKey; + } + + public String getTagValue() { + return tagValue; + } + + public void setTagValue(String tagValue) { + this.tagValue = tagValue; + } + + public boolean isSensitive() { + return isSensitive; + } + + public boolean getIsSensitive() { + return isSensitive; + } + + public void setIsSensitive(boolean isSensitive) { + this.isSensitive = isSensitive; + } }#!/bin/bash # 只读验证:检查 SystemTagDTO 调用点所需方法 rg -n -C2 "SystemTagDTO|setTagKey\\(|getTagKey\\(|setTagValue\\(|setIsSensitive\\(|isSensitive\\(|getIsSensitive\\(" --type java🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.java` around lines 3 - 4, SystemTagDTO is empty but callers expect properties and accessors; add private fields String tagKey, String tagValue and a boolean sensitive (or Boolean) plus public getters/setters: getTagKey/setTagKey, getTagValue/setTagValue, isSensitive/getIsSensitive/setIsSensitive to match usages, and ensure method names match callers (isSensitive vs getIsSensitive) so existing call sites compile when referencing SystemTagDTO.plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java (1)
4720-4722:⚠️ Potential issue | 🟠 Major为 metadata 字段补充
@NoLogging,避免敏感元数据进入日志。
metadata字段可能携带实例敏感信息,建议和本文件其他敏感字段一致做日志脱敏。🛡️ 建议修复
public static class WriteVmInstanceMetadataCmd extends AgentCommand { + `@NoLogging` public String metadata; public String metadataPath; } @@ public static class ReadVmInstanceMetadataRsp extends AgentResponse { + `@NoLogging` public String metadata; }Also applies to: 4732-4733
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines 4720 - 4722, 为避免将敏感元数据写入日志,在 WriteVmInstanceMetadataCmd 类中将其 metadata 字段添加 `@NoLogging` 注解;具体操作是在 WriteVmInstanceMetadataCmd 的 public String metadata 声明前加上 `@NoLogging` 并确保相应的 NoLogging 注解已导入;同样对文件中另一处相同模式的 metadata 字段(评论中提到的重复位置)也应用相同注解以保持一致性。plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2052-2068:⚠️ Potential issue | 🔴 Critical回调链路有阻塞风险:
completion未被完成且回复类型错误。当前实现在回调里使用了
RevertVolumeFromSnapshotOnPrimaryStorageReply并bus.reply(...),但该方法契约是通过completion返回UpdateVmInstanceMetadataOnPrimaryStorageReply。这会导致上层调用一直等待。✅ 建议修复
- public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { + `@Override` + public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); umsg.setMetadata(msg.getMetadata()); umsg.setHostUuid(hostUuid); bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); - bus.send(umsg, new CloudBusCallBack(msg) { + bus.send(umsg, new CloudBusCallBack(completion) { `@Override` public void run(MessageReply r) { - RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); if (!r.isSuccess()) { - reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", msg.getPrimaryStorageUuid()) - .withCause(r.getError())); + completion.fail(Platform.operr( + "failed to update vm metadata for root volume[uuid=%s] on hypervisor.", + msg.getRootVolumeUuid() + ).withCause(r.getError())); + return; } - bus.reply(msg, reply); + completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java` around lines 2052 - 2068, The callback currently constructs a RevertVolumeFromSnapshotOnPrimaryStorageReply and calls bus.reply(...), leaving the provided ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> (completion) uncompleted; replace that with creating an UpdateVmInstanceMetadataOnPrimaryStorageReply inside the CloudBusCallBack.run and invoke completion.success(reply) on success or completion.fail(error) on failure (or completion.complete with the reply if your helper uses that), instead of bus.reply(msg, ...); update error creation to feed into completion.fail so the upstream caller is unblocked (change occurs in handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) inside the CloudBusCallBack.run).plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1928-1952:⚠️ Potential issue | 🟠 Majorbackend 空值校验缺失,存在 NPE 风险。
getUsableBackend()可能返回 null(如无可用集群或主机),但此处未做空值检查即调用backend.handle(...),会导致 NPE。对比本文件其他 handler(如 Line 304-306, 1342-1346, 1456-1460),均有空值校验并返回错误。此外,建议使用
getBackendByHostUuid(hostUuid)根据主机类型选择正确的 backend,以避免多 hypervisor 场景下路由错误。🐛 建议修复
protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid()); if (hostUuid == null || hostUuid.isEmpty()) { reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid())); bus.reply(msg, reply); return; } - final NfsPrimaryStorageBackend backend = getUsableBackend(); + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); + if (backend == null) { + reply.setError(operr("no backend found for host[uuid:%s]", hostUuid)); + bus.reply(msg, reply); + return; + } backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java` around lines 1928 - 1952, The handler handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg) calls getUsableBackend() without null checking which can NPE; change it to fetch the backend by host via getBackendByHostUuid(hostUuid) (to respect host hypervisor) and if the backend is null return an error on reply (like other handlers) instead of calling backend.handle(...); ensure you set reply.setError(operr(...)) with a clear message and bus.reply(msg, reply) when backend is missing.compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java (2)
19-32:⚠️ Potential issue | 🟠 Major
inputVersion可能为 null,导致 NPE。如果 DTO 的
schemaVersion未设置,input.getSchemaVersion()返回 null,Line 29 的compareVersion(inputVersion, CURRENT_VERSION)会因version1.replace("v", "")抛出 NPE。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java` around lines 19 - 32, In upgradeToCurrentVersion, input.getSchemaVersion() may be null causing compareVersion(inputVersion, CURRENT_VERSION) to NPE; add a null check for inputVersion (from VmMetadataDTO#getSchemaVersion) before calling compareVersion and handle it as an older version (e.g., treat null as "v0" or skip the "version too new" branch) so the flow proceeds to upgrade logic instead of calling compareVersion on null; update the checks around CURRENT_VERSION, compareVersion, and any AlertManager/UnsupportedOperationException usage accordingly.
1-6:⚠️ Potential issue | 🔴 Critical重复的包声明与错误的导入路径导致编译失败。
文件包含两个包声明(Line 1 和 Line 3),且导入路径使用了错误的
com.zstack前缀。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java` around lines 1 - 6, Remove the stray second package declaration and fix the wrong com.zstack prefix in imports: keep a single package declaration "org.zstack.compute.vm.metadata" (the one declaring MetadataVersionConverter), delete the erroneous "package com.zstack.core.vmmetadata;" line, and change any incorrect com.zstack imports to the correct org.zstack imports (e.g., ensure GlobalConfig is imported as org.zstack.core.GlobalConfig and AssertUtils as org.zstack.utils.AssertUtils) so the file compiles.compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java (3)
9486-9495:⚠️ Potential issue | 🔴 Critical存在未定义符号与缺失导入,当前代码不可编译。
Line 9486-9495 使用了未声明变量(
vmUuid、volumeVos、snapshotVos、groupVos、tagVos、configVos、snapshotDto),且VmMetadataSerializer未见导入。请删除残留代码或完整补齐变量来源与导入。可用下面脚本做只读核查,预期是这些符号仅有使用、没有声明/导入:
#!/bin/bash set -euo pipefail FILE="compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java" echo "== metadata block ==" sed -n '9480,9500p' "$FILE" echo "== unresolved symbol declaration check ==" for sym in vmUuid volumeVos snapshotVos groupVos tagVos configVos snapshotDto; do echo "--- $sym ---" rg -nP "\\b(String|List<[^>]+>|final\\s+[^=]+|var)\\s+${sym}\\b" "$FILE" || true done echo "== serializer import check ==" rg -n 'import\\s+org\\.zstack\\.compute\\.vm\\.metadata\\.VmMetadataSerializer;' "$FILE" || true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9486 - 9495, The snippet in VmInstanceBase uses undeclared variables (vmUuid, volumeVos, snapshotVos, groupVos, tagVos, configVos, snapshotDto) and an unimported VmMetadataSerializer, causing compile failures; either remove this leftover block or properly source/declare the missing variables from the surrounding method/context and add the import for org.zstack.compute.vm.metadata.VmMetadataSerializer; ensure the code that calls VmMetadataConverter.convert(...) and VmMetadataSerializer.serialize(...) uses correctly scoped variables (or replaces them with available equivalents), and that VolumeSnapshotReferenceVO/ref handling uses a valid snapshotDto obtained earlier in the method before calling VmMetadataConverter.supplementSnapshotReference(...).
9402-9407:⚠️ Potential issue | 🔴 Critical失败分支未回复且重试职责边界混乱。
Line 9402-9407 失败后直接返回,调用方拿不到回复;同时这里直接提交 GC,会与
UpdateVmInstanceMetadataGC的统一重试路径冲突,可能造成 GC 膨胀。🛠️ 建议修复
if (!r.isSuccess()) { reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) .withCause(r.getError())); - - submitUpdateVmInstanceMetadataGC(); + bus.reply(msg, reply); return; } bus.reply(msg, reply);Also applies to: 9412-9424
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9402 - 9407, In VmInstanceBase, the failure branches set reply.setError(...) then simply return and call submitUpdateVmInstanceMetadataGC(), which both fails to send the reply back to the caller and duplicates retry responsibilities with UpdateVmInstanceMetadataGC; change these branches to (1) set the error on the reply and immediately send the reply back to the caller (use the existing reply mechanism used elsewhere in this class), and (2) remove the direct call to submitUpdateVmInstanceMetadataGC() here so the unified UpdateVmInstanceMetadataGC retry path manages retries; apply the same fix to the other failure block around the 9412-9424 region.
9387-9390:⚠️ Potential issue | 🟠 Major根卷查询缺少空值保护,存在直接 NPE 风险。
Line 9389 在
findTuple()返回null时直接tuple.get(...)会抛异常,应先回复错误并返回。🛠️ 建议修复
Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid) .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple(); + if (tuple == null) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("cannot find root volume for vm[uuid:%s]", msg.getUuid())); + bus.reply(msg, reply); + return; + } String primaryStorageUuid = tuple.get(0, String.class); String rootVolumeUuid = tuple.get(1, String.class);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9387 - 9390, The code calls Q.New(...).findTuple() and immediately uses tuple.get(...), which can NPE if findTuple() returns null; in VmInstanceBase (the block using VolumeVO, VolumeVO_, VolumeType.Root and msg.getUuid) check the returned Tuple for null and handle it (log/process an error and return or throw) before assigning primaryStorageUuid and rootVolumeUuid; ensure you provide a clear error path when tuple == null so subsequent tuple.get(...) is never executed.compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java (3)
30-31:⚠️ Potential issue | 🟡 Minor
isSensitiveField缺少空值保护。Line 31 对
key直接matcher,传入null会触发 NPE。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 30 - 31, isSensitiveField currently calls SENSITIVE_PATTERN.matcher(key).find() without null-checking key, which causes an NPE if key is null; update isSensitiveField to first check for null (and optionally empty) and return false early, then run SENSITIVE_PATTERN.matcher(key).find() so matcher is never invoked with a null argument.
17-19:⚠️ Potential issue | 🔴 Critical未解析符号会导致文件无法编译。
当前直接使用了
GlobalConfig、VM_METADATA_SENSITIVE_KEY、AESCipher、AlertManager、SystemTagDTO,但本文件缺少对应可解析导入/静态导入。#!/bin/bash set -e # 验证相关类型/常量在仓库中的实际包路径与定义位置 fd -t f "GlobalConfig.java" fd -t f "AlertManager.java" fd -t f "AESCipher.java" rg -n "VM_METADATA_SENSITIVE_KEY|class SystemTagDTO" --type=java -C 2Also applies to: 42-45, 57-60, 67-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 17 - 19, SensitiveFieldHandler fails to compile due to missing imports/static imports for symbols used; add the appropriate import statements or static imports for GlobalConfig, VM_METADATA_SENSITIVE_KEY (static import if it's a constant), AESCipher, AlertManager, and SystemTagDTO at the top of SensitiveFieldHandler, and ensure the packages match the actual project definitions so references to AES_KEY, AESCipher.encrypt/decrypt, AlertManager, SystemTagDTO and GlobalConfig.resolve correctly; after adding these imports, recompile to confirm the unresolved symbol errors on lines referencing these symbols are fixed.
19-25:⚠️ Potential issue | 🔴 Critical
SENSITIVE_TAGS未初始化,类加载阶段会直接抛 NPE。Line 23 在静态块里对未赋值的
SENSITIVE_TAGS做String.join(),这会在类初始化时失败。建议修复
- private static final List<String> SENSITIVE_TAGS ; + private static final List<String> SENSITIVE_TAGS = java.util.Arrays.asList( + "password", "secret", "token", "credential", "apikey" + ); static { - String regex = String.join("|", SENSITIVE_TAGS); + String regex = String.join("|", SENSITIVE_TAGS); SENSITIVE_PATTERN = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 19 - 25, The static initializer in SensitiveFieldHandler dereferences the uninitialized SENSITIVE_TAGS causing an NPE; initialize SENSITIVE_TAGS before using it (e.g., assign a non-null immutable list or load from configuration) or guard the static block to handle null/empty by providing a default list and then build the regex and assign SENSITIVE_PATTERN; update the static field declaration and the static { ... } block (referencing SENSITIVE_TAGS and SENSITIVE_PATTERN) so SENSITIVE_TAGS is non-null before calling String.join and Pattern.compile.compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java (4)
22-31:⚠️ Potential issue | 🟠 Major
serialize对集合字段缺少判空,容易触发 NPE。Line 24 和 Line 27 默认
getSystemTags()/getResourceConfigs()非空,输入 DTO 只要缺字段就会异常。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java` around lines 22 - 31, The serialize method in VmMetadataSerializer assumes VmMetadataDTO.getSystemTags() and getResourceConfigs() are non-null which can cause NPEs; add null checks and safe defaults: only call SensitiveFieldHandler.filterSensitiveTags and dto.setSystemTags(...) if dto.getSystemTags() != null (or replace null with Collections.emptyList()), and only iterate dto.getResourceConfigs() if getResourceConfigs() != null (or initialize to an empty list) before invoking SensitiveFieldHandler.isSensitiveField(...) and encrypt(...); update serialize to defensively handle null collections so SensitiveFieldHandler.filterSensitiveTags, isSensitiveField, and encrypt are never called on null collections or elements.
63-68:⚠️ Potential issue | 🟠 Major校验和比较存在空指针风险。
storedChecksum可能为null,Line 67 直接storedChecksum.equals(...)会抛 NPE。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java` around lines 63 - 68, The comparison of storedChecksum to calculatedChecksum in VmMetadataSerializer uses storedChecksum.equals(...) which can NPE if dto.getSha256Checksum() is null; update the code that gets dto.getSha256Checksum() to safely compare (e.g., use Objects.equals(storedChecksum, calculatedChecksum) or check for null first) and treat a null storedChecksum as a mismatch (invoke AlertManager.alert the same way), ensuring you still set dto.setSha256Checksum(null) before computing calculatedChecksum and reference the same variables (storedChecksum, calculatedChecksum, AlertManager.alert) when implementing the fix.
3-6:⚠️ Potential issue | 🔴 Critical导入路径与常量引用当前不可解析,存在编译阻塞。
com.zstack.*导入路径与本仓库常见命名空间不一致;同时VM_METADATA_CHECKSUM_ENABLE/VM_METADATA_DEFAULT_VERSION在本文件未定义也未静态导入。#!/bin/bash set -e # 验证正确包路径与常量定义位置 fd -t f "GlobalConfig.java" fd -t f "AlertManager.java" fd -t f "JSONObjectUtil.java" fd -t f "Sha256Utils.java" rg -n "VM_METADATA_CHECKSUM_ENABLE|VM_METADATA_DEFAULT_VERSION" --type=java -C 2Also applies to: 14-16
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java` around lines 3 - 6, The file VmMetadataSerializer imports unresolved packages and references undefined constants; locate the correct package/class names for GlobalConfig, AlertManager, JSONObjectUtil, and Sha256Utils in the repo and update the import statements accordingly, and locate the definitions of VM_METADATA_CHECKSUM_ENABLE and VM_METADATA_DEFAULT_VERSION (e.g., in a GlobalConfig/VM config class) then either add static imports for those constants or qualify them with their defining class (ClassName.VM_METADATA_CHECKSUM_ENABLE, ClassName.VM_METADATA_DEFAULT_VERSION); search the codebase (fd/rg) to find the exact classes to import and replace the incorrect com.zstack.* imports and missing constant references in VmMetadataSerializer.
76-81:⚠️ Potential issue | 🟠 Major
deserialize解密循环同样缺少判空。Line 77 对
dto.getResourceConfigs()直接遍历,空集合场景会中断反序列化流程。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java` around lines 76 - 81, The deserialize method in VmMetadataSerializer calls dto.getResourceConfigs() and iterates it without a null check, which breaks deserialization when resourceConfigs is null; update the deserialize flow (in VmMetadataSerializer.deserialize) to guard dto.getResourceConfigs() before iterating (e.g., check for null and skip decrypt loop or replace with an empty list) and only call SensitiveFieldHandler.decrypt(...) for non-null config values, ensuring config.getConfigKey()/getConfigValue() are safely handled.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (2)
3814-3823:⚠️ Potential issue | 🔴 CriticalLine 3814-3823 未调用
completion,异步调用方可能一直等待。当前只
bus.reply(msg, reply),但方法签名要求通过ReturnValueCompletion回传结果;另外错误文案里的vm[uuid]实际填的是主存储 UUID。建议修复
bus.send(umsg, new CloudBusCallBack(msg) { `@Override` public void run(MessageReply r) { UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); if (!r.isSuccess()) { - reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) - .withCause(r.getError())); + completion.fail(Platform.operr( + "failed to update vm metadata on hypervisor for rootVolume[uuid=%s].", + msg.getRootVolumeUuid() + ).causedBy(r.getError())); + return; } - bus.reply(msg, reply); + completion.success(reply); } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 3814 - 3823, 在 bus.send 的回调里除了 bus.reply(msg, reply) 还需要通过传入的 ReturnValueCompletion 完成异步返回:在成功时调用 completion.success(reply),在失败时用 completion.fail(...)(或相应的 failure 方法)传回 Platform.operr(...) 包含真实错误信息;同时修正 Platform.operr 中的占位符值,不要用 self.getUuid()(主存储 UUID),而应使用实际的 VM UUID(如从 vmInventory 或回调上下文中取得的 vm UUID)以保证错误信息指向正确的实体;修改点位于 LocalStorageKvmBackend 的 bus.send 回调并涉及 UpdateVmInstanceMetadataOnPrimaryStorageReply、msg、self.getUuid() 和 completion。
3802-3807:⚠️ Potential issue | 🔴 CriticalLine 3802-3807 存在空指针路径:
installPath可能为 null。
findValue()未命中时会返回null,Line 3806 直接调用replaceFirst()会触发 NPE。建议修复
- String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue(); + String installPath = Q.New(VolumeVO.class) + .eq(VolumeVO_.uuid, msg.getRootVolumeUuid()) + .select(VolumeVO_.installPath) + .findValue(); + if (installPath == null) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("cannot find root volume installPath for volume[uuid:%s]", msg.getRootVolumeUuid())); + completion.fail(reply.getError()); + return; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 3802 - 3807, The code calls Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(...).findValue() into installPath and then immediately invokes installPath.replaceFirst(...), which can NPE when findValue() returns null; update the block in LocalStorageKvmBackend to check the result of the query (installPath) before using it and handle the null case: detect null after the Q.New(...).findValue() call, log a clear error referencing the root volume UUID, and fail the operation (e.g. throw a CloudRuntimeException or send an error reply) instead of calling replaceFirst; only compute path and metadataPath (using VM_META_SUFFIX) when installPath is non-null.plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java (1)
7382-7387:⚠️ Potential issue | 🟡 Minor失败分支仍设置 metadata,可能污染错误响应。
在 Line [7383]-Line [7386] 中,当
rsp.isSuccess()为false时仍执行reply.setMetadata(rsp.metadata)。建议仅在成功时设置 metadata。修复建议
public void success(ReadVmInstanceMetadataRsp rsp) { if (!rsp.isSuccess()) { reply.setError(operr("operation error, because:%s", rsp.getError())); + } else { + reply.setMetadata(rsp.metadata); } - reply.setMetadata(rsp.metadata); bus.reply(msg, reply); completion.done(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java` around lines 7382 - 7387, In the success(ReadVmInstanceMetadataRsp rsp) callback, avoid populating reply.metadata when rsp.isSuccess() is false: change the flow in success(ReadVmInstanceMetadataRsp) so that if (!rsp.isSuccess()) you set reply.setError(operr(...)) and immediately reply via bus.reply(msg, reply) (or return/else) and only call reply.setMetadata(rsp.metadata) in the successful branch; ensure you still call bus.reply(msg, reply) exactly once (using the same reply object) and do not overwrite error responses with rsp.metadata.compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java (5)
1-4:⚠️ Potential issue | 🔴 Critical移除重复的 package 声明
文件包含两个 package 声明,将导致编译失败。第 3 行的
package com.zstack.core.vmmetadata;必须删除。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 1 - 4, The file contains two package declarations which breaks compilation: remove the duplicate package declaration `package com.zstack.core.vmmetadata;` and keep the correct single package line `package org.zstack.compute.vm.metadata;` at the top of VmMetadataConverter.java so the class compiles with a single package statement.
27-55:⚠️ Potential issue | 🟠 Major对可空集合做空值防护,避免 NPE
convert(...)方法直接对入参列表调用stream(),当调用方传入null时会触发 NPE。建议统一用空集合兜底或添加 null 检查。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 27 - 55, The convert(...) method calls downstream converters with list parameters that may be null and cause NPEs; ensure each list parameter is null-safe before use by normalizing incoming lists to empty collections (e.g. wrap vmNicVos, volumeVos, snapshotVos, snapshotGroupVos, systemTagVos, resourceConfigVos with a null-to-empty conversion) or by updating the called helpers (convertVmNic, convertVolume, convertSnapshot, convertSnapshotGroup, convertSystemTag, convertResourceConfig) to accept and handle null. Locate the convert(...) method and replace direct uses of the raw lists with null-safe equivalents (or add null checks) so stream() or iteration never runs on a null reference.
132-137:⚠️ Potential issue | 🔴 Critical缺少
VolumeSnapshotGroupRefVO的导入声明第 135 行使用了
VolumeSnapshotGroupRefVO::getSnapshotUuid,但该类未在文件头部导入。🐛 建议修复
import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO; import org.zstack.header.tag.SystemTagVO;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 132 - 137, Add an import for VolumeSnapshotGroupRefVO to resolve the missing symbol used in VmMetadataConverter where dto.setSnapshotUuidList(...) maps using VolumeSnapshotGroupRefVO::getSnapshotUuid; locate the VmMetadataConverter class and add the appropriate import for org.zstack.storage.snapshot.group.VolumeSnapshotGroupRefVO (or the correct package where VolumeSnapshotGroupRefVO is defined) at the top of the file so the stream mapping compiles.
192-207:⚠️ Potential issue | 🔴 Critical添加缺失的导入声明:
VolumeSnapshotReferenceVO和VolumeSnapshotReferenceTreeVO
supplementSnapshotReference和supplementSnapshotTree方法在方法参数中使用了VolumeSnapshotReferenceVO和VolumeSnapshotReferenceTreeVO,但这两个类均未导入,导致编译失败。需要添加以下导入:import org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceVO; import org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceTreeVO;另外,文件存在严重的结构问题:第 1 行和第 3 行存在两个
package声明,这是无效的 Java 语法,必须删除其中一个。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 192 - 207, The file fails to compile because VolumeSnapshotReferenceVO and VolumeSnapshotReferenceTreeVO are used in supplementSnapshotReference and supplementSnapshotTree but not imported, and the file contains duplicate package declarations; add the two imports "org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceVO" and "org.zstack.header.storage.snapshot.reference.VolumeSnapshotReferenceTreeVO" to the top imports and remove the extra package declaration so there is only a single package line, then recompile to ensure supplementSnapshotReference(SnapshotMetadataDTO, VolumeSnapshotReferenceVO) and supplementSnapshotTree(SnapshotGroupMetadataDTO, VolumeSnapshotReferenceTreeVO) resolve correctly.
50-50:⚠️ Potential issue | 🔴 Critical删除重复的包声明,添加缺失的
GlobalConfig导入第 1-3 行存在 Java 语法错误:文件包含两个包声明,这会导致编译失败。第 50 行引用的
GlobalConfig也未被导入。🔴 必须修复的问题
文件头部包含两个包声明:
package org.zstack.compute.vm.metadata; package com.zstack.core.vmmetadata;应删除第二个包声明,并补充缺失的导入:
package org.zstack.compute.vm.metadata; -package com.zstack.core.vmmetadata; - import org.zstack.header.storage.snapshot.VolumeSnapshotVO; import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import com.zstack.core.GlobalConfig; import org.zstack.header.tag.SystemTagVO;注:
VM_METADATA_DEFAULT_VERSION是传递给GlobalConfig.getString()的配置键字符串(如 "vm.metadata.version"),无需单独导入,仅需导入GlobalConfig类本身。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` at line 50, The file has an extra/duplicate package declaration ("package com.zstack.core.vmmetadata;") at the top which must be removed and the missing import for GlobalConfig must be added so the reference in VmMetadataConverter (where dto.setSchemaVersion(GlobalConfig.getString(VM_METADATA_DEFAULT_VERSION)) is called) compiles; remove the second package line, keep the correct package declaration (package org.zstack.compute.vm.metadata;), and add an import for the GlobalConfig class (so GlobalConfig.getString(...) resolves) in the imports of the VmMetadataConverter class.compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java (2)
47-53:⚠️ Potential issue | 🔴 Critical编译错误:引用了未声明的字段
primaryStorageUuid
getPrimaryStorageUuid()和setPrimaryStorageUuid()方法引用了primaryStorageUuid字段,但该字段未在类中声明(Lines 6-26 的字段列表中缺失),将导致编译失败。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java` around lines 47 - 53, The class VolumeMetadataDTO declares static accessor methods getPrimaryStorageUuid() and setPrimaryStorageUuid() but the backing field primaryStorageUuid is missing; add a matching field declaration (e.g. a private static String primaryStorageUuid) to the class fields so the static getters/setters compile and reference the correct variable.
6-26:⚠️ Potential issue | 🔴 Critical严重设计缺陷:DTO 字段不应声明为 static
所有字段被声明为
public static,这会导致:
- 线程安全问题:所有
VolumeMetadataDTO实例共享同一状态,并发操作会相互覆盖数据- 语义错误:DTO 应该表示每个实例独立的数据,而非类级别共享
应将所有
static字段改为实例字段,并将对应的 getter/setter 改为实例方法。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java` around lines 6 - 26, The DTO fields in VolumeMetadataDTO (e.g., description, name, vmInstanceUuid, rootImageUuid, diskOfferingUuid, format, installPath, type, size, actualSize, deviceId, state, status, createDate, lastOpDate, lastDetachDate, lastAttachDate, lastVmInstanceUuid, isShareable, volumeQos, protocol) are incorrectly declared static; change each to an instance field (remove static) and update any existing getters/setters to be instance methods (non-static) so each VolumeMetadataDTO holds its own state and avoids shared-class state and threading issues.header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java (1)
59-62: 🛠️ Refactor suggestion | 🟠 Major
__example__()方法应填充必需字段的示例值根据编码规范,API 类需要实现
__example__方法以便生成 API 文档。当前实现返回空对象,应为必需字段(metadataPath、primaryStorageUuid、clusterUuid)赋予示例值。参考
APIGetVmInstanceMetadataFromPrimaryStorageMsg.__example__()的实现:msg.setUuid(uuid());♻️ 建议修改
public static APIRegisterVmInstanceMsg __example__() { APIRegisterVmInstanceMsg msg = new APIRegisterVmInstanceMsg(); + msg.setMetadataPath("/zstack_ps/vm-metadata/vm-uuid.json"); + msg.setPrimaryStorageUuid(uuid()); + msg.setClusterUuid(uuid()); return msg; }As per coding guidelines: "API 类需要实现
__example__方法以便生成 API 文档"。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java` around lines 59 - 62, Fill the APIRegisterVmInstanceMsg.__example__() with sample values for required fields: set metadataPath to a representative string (e.g. "/path/to/metadata"), and set primaryStorageUuid and clusterUuid using the uuid() helper (like APIGetVmInstanceMetadataFromPrimaryStorageMsg.__example__() does with msg.setUuid(uuid())); ensure the method returns the populated APIRegisterVmInstanceMsg instance.
🧹 Nitpick comments (4)
compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.java (1)
3-15: 枚举常量命名与注释语义不一致。
STORAGE的注释描述的是"运行时变更,通过消息通知 VM 实时更新",但STORAGE这个名称更像是指存储相关的变更,容易产生歧义。建议考虑更具描述性的命名,如RUNTIME或LIVE_UPDATE,以更准确地表达其语义。另外,
NONE缺少注释,建议补充以保持一致性。♻️ 建议重命名以提升可读性
public enum MetadataImpactLevel { /** * 配置变更:仅更新数据库中的元数据,VM 运行时不感知,适用于大多数场景 */ CONFIG, /** * 运行时变更:除了更新数据库,还需要通过消息通知 VM 实时更新元数据,适用于需要即时生效的场景 */ - STORAGE, + RUNTIME, + /** + * 无影响:不需要更新元数据 + */ NONE }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.java` around lines 3 - 15, Enum constant STORAGE in MetadataImpactLevel is misnamed versus its javadoc — update the enum to use a clearer name (e.g., RUNTIME or LIVE_UPDATE) instead of STORAGE and update its javadoc to match "运行时变更:通过消息通知 VM 实时更新元数据"; also add a descriptive javadoc for NONE to match the style of CONFIG and the new runtime constant; adjust any usages of MetadataImpactLevel.STORAGE across the codebase to the new constant name (e.g., MetadataImpactLevel.RUNTIME) to keep references consistent.compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java (1)
137-141: 建议添加@GlobalConfigDef注解。对比文件中其他配置项(如
VENDOR_ID、VM_CLOCK_SYNC_INTERVAL_IN_SECONDS),建议为新增的GC_INTERVAL和VM_METADATA添加@GlobalConfigDef注解,明确定义defaultValue、type和description,以提升配置的可维护性和文档完整性。♻️ 建议添加配置定义注解
+ `@GlobalConfigDef`(defaultValue = "300", type = Long.class, description = "GC interval in seconds for VM deletion") `@GlobalConfigValidation`(numberGreaterThan = 1) public static GlobalConfig GC_INTERVAL = new GlobalConfig(CATEGORY, "deletion.gcInterval"); + `@GlobalConfigDef`(defaultValue = "false", type = Boolean.class, description = "Enable VM metadata feature") `@GlobalConfigValidation`(validValues = {"true", "false"}) public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java` around lines 137 - 141, Add `@GlobalConfigDef` annotations for GC_INTERVAL and VM_METADATA similar to VENDOR_ID/VM_CLOCK_SYNC_INTERVAL_IN_SECONDS: for GC_INTERVAL (symbol GC_INTERVAL) add `@GlobalConfigDef`(defaultValue = "60", type = GlobalConfigType.Integer, description = "Garbage collection interval in seconds") above the field; for VM_METADATA (symbol VM_METADATA) add `@GlobalConfigDef`(defaultValue = "false", type = GlobalConfigType.Boolean, description = "Enable VM metadata exposure") above the field, keeping the existing `@GlobalConfigValidation` annotations.header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java (1)
3-8: 建议为接口方法补充 Javadoc,避免契约语义不清。当前 3 个接口方法没有文档说明,后续实现方难以统一
installPath重写规则与返回约定。📝 建议补充
public interface RegisterVmInstanceException { + /** + * 根据主存储实现重写卷安装路径。 + * `@param` installPath 原始路径 + * `@return` 重写后的路径 + */ String updateVolumeInstallPath(String installPath); + /** + * 根据主存储实现重写快照安装路径。 + * `@param` installPath 原始路径 + * `@return` 重写后的路径 + */ String updateVolumeSnapshotInstallPath(String installPath); + /** + * 返回对应的主存储类型。 + */ PrimaryStorageType getPrimaryStorageType(); }As per coding guidelines: “接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java` around lines 3 - 8, Add Javadoc to the RegisterVmInstanceException interface and its three methods—updateVolumeInstallPath(String installPath), updateVolumeSnapshotInstallPath(String installPath), and getPrimaryStorageType()—specifying the semantic contract: what installPath represents, when implementations should rewrite it (and any normalization rules), what a null/empty or unchanged return value means, and that getPrimaryStorageType() must return the PrimaryStorageType this rule applies to; keep method signatures without extra modifiers and ensure param and return tags are present in each Javadoc.compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java (1)
179-181: 重试上限后的运维告警仍是 TODO,建议尽快补齐。超过最大重试后目前只记录日志,缺少可观测系统告警链路。
如果你希望,我可以直接给出一个最小可落地的告警接入方案并帮你拆成独立 issue。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java` around lines 179 - 181, The TODO under UpdateVmInstanceMetadataGC (where retries exceed max) leaves only a log entry and must instead raise an operational alert: implement a call to the existing alerting/integration utility (or add one) to create/send a ZWatch alarm named "更新虚拟机元数据失败" including the primary storage UUID and VM id/context, and ensure the alarm payload contains retry count, last error message and timestamp; update the failure handling block in UpdateVmInstanceMetadataGC (the branch that runs after maxRetries) to invoke that alert API, handle any errors from the alert call (log on failure) and add a unit/integration test or small e2e check to exercise the alarm path so the monitoring chain is verified.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xml
📒 Files selected for processing (61)
compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.javacompute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupRefMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmNicMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.javacore/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.javacore/src/main/java/org/zstack/core/gc/SubmitTimeBasedGarbageCollectorMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.javaheader/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.javaheader/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEvent.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/VmInstanceConstant.javaheader/src/main/java/org/zstack/header/vm/VmMetadata.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMHost.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceResult.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy
✅ Files skipped from review due to trivial changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
🚧 Files skipped from review as they are similar to previous changes (19)
- header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- core/src/main/java/org/zstack/core/gc/SubmitTimeBasedGarbageCollectorMsg.java
- plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- compute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupMetadataDTO.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java
- header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.java
- compute/src/main/java/org/zstack/compute/vm/metadata/SnapshotMetadataDTO.java
- compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.java
| @Component | ||
| public class VmUuidResolverRegistry { | ||
| private Map<Class<?>, VmUuidResolver> resolvers = new HashMap<>(); | ||
| private VmUuidResolver defaultResolver = new DefaultVmUuidResolver(); | ||
|
|
||
| @PostConstruct | ||
| public void init() { | ||
| // 注册特定 API 的解析器 | ||
| VolumeBasedVmUuidResolver volumeResolver = new VolumeBasedVmUuidResolver(); | ||
| resolvers.put(APICreateVolumesSnapshotMsg.class, volumeResolver); | ||
| resolvers.put(APICreateVolumeSnapshotGroupMsg.class, volumeResolver); | ||
| resolvers.put(APIAttachDataVolumeToVmMsg.class, volumeResolver); | ||
| resolvers.put(APIDetachDataVolumeFromVmMsg.class, volumeResolver); | ||
|
|
||
| ResourceBasedVmUuidResolver resourceResolver = new ResourceBasedVmUuidResolver(); | ||
| resolvers.put(APICreateSystemTagMsg.class, resourceResolver); | ||
| resolvers.put(APIDeleteTagMsg.class, resourceResolver); | ||
| resolvers.put(APIUpdateSystemTagMsg.class, resourceResolver); | ||
| resolvers.put(APIUpdateResourceConfigMsg.class, resourceResolver); | ||
| resolvers.put(APIDeleteResourceConfigMsg.class, resourceResolver); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's find the file and check its general structure
fd "MetadataImpact.java" compute/src/main/java/org/zstack/compute/vm/metadata/
# Get the file size to understand how much we can read
wc -l compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.javaRepository: MatheMatrix/zstack
Length of output: 211
🏁 Script executed:
#!/bin/bash
# Check the imports and examine lines around 175-194
head -50 compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | cat -nRepository: MatheMatrix/zstack
Length of output: 1688
🏁 Script executed:
#!/bin/bash
# Get the specific lines mentioned in the review (175-194) plus context
sed -n '160,210p' compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | cat -nRepository: MatheMatrix/zstack
Length of output: 2128
🏁 Script executed:
#!/bin/bash
# Search for the resolver class definitions and their `@Autowired` fields
grep -n "class VolumeBasedVmUuidResolver\|class ResourceBasedVmUuidResolver\|@Autowired.*dbf\|private.*DatabaseFacade" compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | head -30Repository: MatheMatrix/zstack
Length of output: 291
🏁 Script executed:
#!/bin/bash
# Check what `@Component` annotation is imported
grep -n "import.*Component\|import.*Plexus\|import.*spring" compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | cat -nRepository: MatheMatrix/zstack
Length of output: 202
🏁 Script executed:
#!/bin/bash
# Check lines 65-70 and 102-107 for `@Autowired` annotation
sed -n '65,70p' compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | cat -n
echo "---"
sed -n '102,107p' compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java | cat -nRepository: MatheMatrix/zstack
Length of output: 457
🏁 Script executed:
#!/bin/bash
# Also check if there are any PostConstruct imports
grep -n "import.*PostConstruct\|import.*Map\|import.*HashMap" compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.javaRepository: MatheMatrix/zstack
Length of output: 44
使用 Plexus @Component 和手工 new 实例化导致依赖注入失败,运行时会触发 NPE
VmUuidResolverRegistry 使用的是 Plexus 的 @Component(第 3 行),而非 Spring 的 @Component,Spring 不会将其作为 Bean 管理。同时,VolumeBasedVmUuidResolver 和 ResourceBasedVmUuidResolver 在 init() 方法中通过 new 手工实例化(第 184、190 行),这些解析器虽然依赖 @Autowired private DatabaseFacade dbf,但通过 new 创建的实例无法获得 Spring 注入,dbf 将为 null,导致运行时 NPE。
此外,文件还缺少必要的导入:java.util.Map、java.util.HashMap、javax.annotation.PostConstruct,会导致编译失败。
需要改用构造注入模式:将 dbf 通过构造器参数传入解析器,并在 VmUuidResolverRegistry 中注入 dbf;同时将 @Component 改为 Spring 的实现。
建议修复
-import org.codehaus.plexus.component.annotations.Component;
+import org.springframework.stereotype.Component;
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Map;
@@
public class VolumeBasedVmUuidResolver implements VmUuidResolver {
- `@Autowired`
- private DatabaseFacade dbf;
+ private final DatabaseFacade dbf;
+
+ public VolumeBasedVmUuidResolver(DatabaseFacade dbf) {
+ this.dbf = dbf;
+ }
@@
public class ResourceBasedVmUuidResolver implements VmUuidResolver {
- `@Autowired`
- private DatabaseFacade dbf;
+ private final DatabaseFacade dbf;
+
+ public ResourceBasedVmUuidResolver(DatabaseFacade dbf) {
+ this.dbf = dbf;
+ }
@@
`@Component`
public class VmUuidResolverRegistry {
+ `@Autowired`
+ private DatabaseFacade dbf;
@@
- VolumeBasedVmUuidResolver volumeResolver = new VolumeBasedVmUuidResolver();
+ VolumeBasedVmUuidResolver volumeResolver = new VolumeBasedVmUuidResolver(dbf);
@@
- ResourceBasedVmUuidResolver resourceResolver = new ResourceBasedVmUuidResolver();
+ ResourceBasedVmUuidResolver resourceResolver = new ResourceBasedVmUuidResolver(dbf);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java`
around lines 175 - 194, VmUuidResolverRegistry is annotated with the wrong
Plexus `@Component` and creates VolumeBasedVmUuidResolver and
ResourceBasedVmUuidResolver with new(), so their `@Autowired` DatabaseFacade
fields are never injected and cause NPEs; fix by using Spring's `@Component`
(org.springframework.stereotype.Component) on VmUuidResolverRegistry, inject
DatabaseFacade into the registry (constructor or field), add missing imports
(java.util.Map, java.util.HashMap, javax.annotation.PostConstruct), and modify
VolumeBasedVmUuidResolver and ResourceBasedVmUuidResolver to accept
DatabaseFacade via a constructor (remove their field-level `@Autowired`), then
instantiate them in VmUuidResolverRegistry.init() with new
VolumeBasedVmUuidResolver(dbf) and new ResourceBasedVmUuidResolver(dbf) so
dependencies are provided explicitly.
| */ | ||
| public class MetadataVersionConverter { | ||
| // 当前支持的最高版本 | ||
| private static final String CURRENT_VERSION = GlobalConfig.getString(VM_METADATA_DEFAULT_VERSION); |
There was a problem hiding this comment.
未定义的常量 VM_METADATA_DEFAULT_VERSION。
VM_METADATA_DEFAULT_VERSION 未在当前作用域中定义或导入,会导致编译失败。根据 PR 上下文,应使用类似 VmGlobalConfig.VM_METADATA_DEFAULT_VERSION.value() 的方式获取配置值。
🐛 建议修复
+import org.zstack.compute.vm.VmGlobalConfig;
+
public class MetadataVersionConverter {
// 当前支持的最高版本
- private static final String CURRENT_VERSION = GlobalConfig.getString(VM_METADATA_DEFAULT_VERSION);
+ private static final String CURRENT_VERSION = VmGlobalConfig.VM_METADATA_DEFAULT_VERSION.value();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`
at line 14, MetadataVersionConverter declares CURRENT_VERSION using an undefined
symbol VM_METADATA_DEFAULT_VERSION—replace that with the proper global config
accessor (e.g., use VmGlobalConfig.VM_METADATA_DEFAULT_VERSION.value() inside
GlobalConfig.getString(...) or call
VmGlobalConfig.VM_METADATA_DEFAULT_VERSION.value() directly) and add the
necessary import for VmGlobalConfig; update the CURRENT_VERSION initialization
to reference VmGlobalConfig.VM_METADATA_DEFAULT_VERSION.value() so the code
compiles.
| AlertManager.alert(String.format("不支持的元数据版本: %s, 当前最高版本: %s", inputVersion, CURRENT_VERSION)); | ||
| throw new UnsupportedOperationException("元数据版本过高,不支持加载"); |
There was a problem hiding this comment.
未导入的类 AlertManager。
AlertManager 类未导入,会导致编译失败。需要添加正确的导入语句。
🐛 建议修复
+import org.zstack.core.alert.AlertManager;或者如果 AlertManager 是静态方法调用,需要确认正确的类路径并添加相应导入。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`
around lines 30 - 31, The class MetadataVersionConverter references AlertManager
but there is no import, causing compilation failure; add the proper import for
the AlertManager class used in the file (or replace the call with a
fully-qualified class name) so AlertManager.alert(...) resolves correctly,
ensuring you keep the existing usage with inputVersion and CURRENT_VERSION in
the exception/logging block.
| retryCount++; | ||
|
|
||
| // 指数退避:baseDelay * 2^retryCount,上限 2^MAX_EXPONENT 防止左移溢出 | ||
| int exponent = Math.min(retryCount, MAX_EXPONENT); | ||
| NEXT_TIME = BASE_DELAY_SECONDS * (1L << exponent); | ||
| NEXT_TIME_UNIT = TimeUnit.SECONDS; |
There was a problem hiding this comment.
指数退避首轮延迟偏大一档(20s 而不是注释中的 10s)。
当前先 retryCount++ 再 1 << exponent,第一次失败会得到 20s。
🔧 建议修复
private void onUpdateFail(GCCompletion completion, ErrorCode err) {
retryCount++;
- int exponent = Math.min(retryCount, MAX_EXPONENT);
+ int exponent = Math.min(Math.max(retryCount - 1, 0), MAX_EXPONENT);
NEXT_TIME = BASE_DELAY_SECONDS * (1L << exponent);
NEXT_TIME_UNIT = TimeUnit.SECONDS;📝 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.
| retryCount++; | |
| // 指数退避:baseDelay * 2^retryCount,上限 2^MAX_EXPONENT 防止左移溢出 | |
| int exponent = Math.min(retryCount, MAX_EXPONENT); | |
| NEXT_TIME = BASE_DELAY_SECONDS * (1L << exponent); | |
| NEXT_TIME_UNIT = TimeUnit.SECONDS; | |
| retryCount++; | |
| // 指数退避:baseDelay * 2^retryCount,上限 2^MAX_EXPONENT 防止左移溢出 | |
| int exponent = Math.min(Math.max(retryCount - 1, 0), MAX_EXPONENT); | |
| NEXT_TIME = BASE_DELAY_SECONDS * (1L << exponent); | |
| NEXT_TIME_UNIT = TimeUnit.SECONDS; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java`
around lines 344 - 349, In UpdateVmInstanceMetadataGC adjust the exponential
backoff calculation so the first retry uses BASE_DELAY_SECONDS * 2^0 (i.e. 10s)
by not incrementing retryCount before computing exponent; compute exponent from
the current retryCount (or use retryCount - 1) then set NEXT_TIME =
BASE_DELAY_SECONDS * (1L << exponent) and NEXT_TIME_UNIT = TimeUnit.SECONDS, and
only then increment retryCount (or otherwise ensure exponent uses the previous
count); reference retryCount, MAX_EXPONENT, BASE_DELAY_SECONDS, NEXT_TIME,
NEXT_TIME_UNIT in the fix.
| private String extractVmInstanceUuid(APIMessage msg) { | ||
| try { | ||
| Method method = msg.getClass().getMethod("getVmInstanceUuid"); | ||
| return (String) method.invoke(msg); | ||
| } catch (NoSuchMethodException e) { | ||
| // 尝试 getResourceUuid 作为 fallback | ||
| try { | ||
| Method method = msg.getClass().getMethod("getResourceUuid"); | ||
| return (String) method.invoke(msg); | ||
| } catch (Exception ex) { | ||
| logger.warn(String.format("cannot extract vmInstanceUuid from %s", msg.getClass().getName())); | ||
| return null; | ||
| } | ||
| } catch (Exception e) { | ||
| logger.warn(String.format("failed to extract vmInstanceUuid from %s: %s", | ||
| msg.getClass().getName(), e.getMessage())); | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
VM UUID 提取逻辑与现有解析机制不一致,可能导致元数据更新遗漏
当前仅反射 getVmInstanceUuid(),失败后回退 getResourceUuid()。对按卷/快照反推 VM 的消息,resourceUuid 可能不是 VM UUID。建议复用 compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java 中的 VmUuidResolverRegistry,按消息类型解析并支持多 VM UUID 场景。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java`
around lines 112 - 130, Replace the fragile reflection-based logic in
extractVmInstanceUuid with the centralized resolver: use the
VmUuidResolverRegistry (from MetadataImpact.java) to resolve VM UUIDs for the
given APIMessage instead of calling getVmInstanceUuid/getResourceUuid directly;
call the registry's resolve method for the message, handle the returned
collection (if empty return null, if one UUID return that UUID, if multiple
log/debug the multiple-VM case and decide/return the primary UUID or null per
existing caller expectations), and remove the reflection branches (keep warning
logs for unexpected failures).
compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java
Outdated
Show resolved
Hide resolved
| String json = VmMetadataSerializer.serialize(vmDto); | ||
|
|
||
| // | ||
| VolumeSnapshotReferenceVO refVo = dbf.findByUuid(snapshotDto.getSnapshotUuid(), VolumeSnapshotReferenceVO.class); | ||
| VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo); | ||
|
|
||
| return JSONObjectUtil.toJsonString(vmMetadata); |
There was a problem hiding this comment.
元数据返回路径绕过序列化安全处理,存在敏感信息暴露风险。
Line 9491 已生成 json,但 Line 9497 返回的是原始 vmMetadata 序列化;同时 Line 9500-9511 直接序列化 SystemTagVO/ResourceConfigVO 全量内容,脱敏/加密链路未生效。
🛠️ 建议修复方向
- String json = VmMetadataSerializer.serialize(vmDto);
+ String json = VmMetadataSerializer.serialize(vmDto);
...
- return JSONObjectUtil.toJsonString(vmMetadata);
+ return json;并移除或收敛 getResourceSystemTagFromDb/getResourceConfigFromDb 的全量 VO 序列化路径,统一走 DTO + Serializer。
Also applies to: 9500-9511
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines
9491 - 9497, The code currently serializes vmDto via
VmMetadataSerializer.serialize into the local variable json but then returns the
raw vmMetadata (JSONObjectUtil.toJsonString(vmMetadata)), bypassing the
serializer and exposing sensitive fields; change the return to use the
already-produced json from VmMetadataSerializer.serialize (i.e., return the
serialized output), and refactor
getResourceSystemTagFromDb/getResourceConfigFromDb paths to stop serializing
SystemTagVO/ResourceConfigVO directly — instead convert those VOs to DTOs and
serialize them with the appropriate serializer used by VmMetadataSerializer
(ensure VmMetadataConverter.supplementSnapshotReference is applied to DTOs
before serialization) so all metadata, tags and resource configs go through the
unified DTO+Serializer pipeline.
| @RestRequest( | ||
| path = "/primary-storage/vm-instances/metadata", | ||
| method = HttpMethod.GET, | ||
| responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class | ||
| ) | ||
| public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements PrimaryStorageMessage { | ||
| @APIParam(resourceType = PrimaryStorageVO.class) | ||
| private String uuid; |
There was a problem hiding this comment.
GET 路径缺少 {uuid} 占位符,必填参数与路由定义不一致。
uuid 是必填字段,但 Line [10] 的 path 没有体现资源变量绑定,调用与文档会出现歧义。
🔧 建议修复
`@RestRequest`(
- path = "/primary-storage/vm-instances/metadata",
+ path = "/primary-storage/{uuid}/vm-instances/metadata",
method = HttpMethod.GET,
responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class
)As per coding guidelines: “当 path 中引用消息类变量时,使用 {variableName} 格式。”
📝 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.
| @RestRequest( | |
| path = "/primary-storage/vm-instances/metadata", | |
| method = HttpMethod.GET, | |
| responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class | |
| ) | |
| public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements PrimaryStorageMessage { | |
| @APIParam(resourceType = PrimaryStorageVO.class) | |
| private String uuid; | |
| `@RestRequest`( | |
| path = "/primary-storage/{uuid}/vm-instances/metadata", | |
| method = HttpMethod.GET, | |
| responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class | |
| ) | |
| public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements PrimaryStorageMessage { | |
| `@APIParam`(resourceType = PrimaryStorageVO.class) | |
| private String uuid; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`
around lines 9 - 16, The RestRequest path in
APIGetVmInstanceMetadataFromPrimaryStorageMsg does not include the required
{uuid} placeholder that corresponds to the `@APIParam` private String uuid; add
the path variable to the RestRequest annotation so the route and required field
match (e.g. change path to include "{uuid}" such as
"/primary-storage/{uuid}/vm-instances/metadata"), keeping the class
APIGetVmInstanceMetadataFromPrimaryStorageMsg and the uuid field intact.
| ReadVmInstanceMetadataOnHypervisorMsg umsg = new ReadVmInstanceMetadataOnHypervisorMsg(); | ||
| umsg.setHostUuid(msg.getHostUuid()); | ||
| umsg.setMetadataPath(msg.getMetadataPath()); | ||
| bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, msg.getHostUuid()); | ||
| bus.send(umsg, new CloudBusCallBack(msg) { |
There was a problem hiding this comment.
hostUuid 是可选参数却被无保护使用,空值会导致路由失败。
Line [1881] 和 Line [1883] 直接使用 msg.getHostUuid(),而 APIRegisterVmInstanceMsg 中该字段是 required = false。
🔧 建议修复
`@Override`
public void run(FlowTrigger trigger, Map data) {
+ if (msg.getHostUuid() == null) {
+ trigger.fail(operr("hostUuid is required when reading vm metadata from hypervisor"));
+ return;
+ }
+
ReadVmInstanceMetadataOnHypervisorMsg umsg = new ReadVmInstanceMetadataOnHypervisorMsg();
umsg.setHostUuid(msg.getHostUuid());
umsg.setMetadataPath(msg.getMetadataPath());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1880 - 1884, ReadVmInstanceMetadataOnHypervisorMsg construction
uses msg.getHostUuid() without null checks but APIRegisterVmInstanceMsg.hostUuid
is optional; guard against null by checking msg.getHostUuid() before calling
umsg.setHostUuid(...) and before bus.makeTargetServiceIdByResourceUuid(...); if
hostUuid is null, avoid the resource-based routing and instead target the host
service by service id (e.g., use bus.makeTargetServiceIdByServiceId(umsg,
HostConstant.SERVICE_ID) or otherwise set the message's target service id to
HostConstant.SERVICE_ID) so routing won't fail when hostUuid is absent.
| GetVmInstanceMetadataFromPrimaryStorageMsg gmsg = new GetVmInstanceMetadataFromPrimaryStorageMsg(); | ||
| gmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); | ||
| bus.makeTargetServiceIdByResourceUuid(gmsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); | ||
|
|
There was a problem hiding this comment.
查询消息已发送但未在本地分发器注册,API 路径当前不可达。
Line [2185] 构造并发送了 GetVmInstanceMetadataFromPrimaryStorageMsg,但 handleLocalMessage(...) 没有对应 instanceof 分支,最终会落到 unknown message。
🔧 建议修复
} else if (msg instanceof CleanUpStorageTrashOnPrimaryStorageMsg) {
handle((CleanUpStorageTrashOnPrimaryStorageMsg)msg);
+ } else if (msg instanceof GetVmInstanceMetadataFromPrimaryStorageMsg) {
+ handle((GetVmInstanceMetadataFromPrimaryStorageMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 2185 - 2188, PrimaryStorageBase creates and sends
GetVmInstanceMetadataFromPrimaryStorageMsg but handleLocalMessage(...) lacks a
corresponding instanceof branch, causing the message to be treated as unknown;
update PrimaryStorageBase.handleLocalMessage to add an instanceof check for
GetVmInstanceMetadataFromPrimaryStorageMsg (or its handler method name if one
exists) and dispatch it to the existing processing routine (or implement the new
handler) so local messages of type GetVmInstanceMetadataFromPrimaryStorageMsg
are properly handled and routed via the local dispatcher.
adb1c07 to
cf22965
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (27)
compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java (2)
132-151:⚠️ Potential issue | 🔴 Critical修复未定义符号与参数透传缺失:当前代码会直接编译失败。
submitUpdateVmInstanceMetadataGC()内使用了未定义的self/msg,且上游vmInstanceUuid没有透传到该方法。🔧 建议修复
private void submitMetadataUpdateGCJob(String vmInstanceUuid, MetadataImpactType[] types) { logger.debug(String.format("API succeeded, submitting metadata update GC for vm[uuid:%s]", vmInstanceUuid)); // TODO: 通过 GCFacade 提交 GC 任务更新虚拟机元数据 - submitUpdateVmInstanceMetadataGC(); + submitUpdateVmInstanceMetadataGC(vmInstanceUuid); } -private void submitUpdateVmInstanceMetadataGC() { +private void submitUpdateVmInstanceMetadataGC(String vmInstanceUuid) { SubmitTimeBasedGarbageCollectorMsg gcmsg = new SubmitTimeBasedGarbageCollectorMsg(); gcmsg.setGcInterval(VmGlobalConfig.GC_INTERVAL.value(Long.class)); gcmsg.setUnit(TimeUnit.SECONDS); UpdateVmInstanceMetadataGC gc = new UpdateVmInstanceMetadataGC(); - gc.vmInstanceUuid = self.getUuid(); - gc.NAME = String.format("gc-update-vm-%s-metadata", msg.getVmInstanceUuid()); + gc.vmInstanceUuid = vmInstanceUuid; + gc.NAME = String.format("gc-update-vm-%s-metadata", vmInstanceUuid); gcmsg.setGc(gc); - bus.makeTargetServiceIdByResourceUuid(gcmsg, GCConstants.SERVICE_ID, msg.getVmInstanceUuid()); + bus.makeTargetServiceIdByResourceUuid(gcmsg, GCConstants.SERVICE_ID, vmInstanceUuid); bus.send(gcmsg); }可用以下只读脚本做快速核验(应看到:
submitUpdateVmInstanceMetadataGC无参签名 + 方法体引用self/msg+ 类字段中无self/msg声明):#!/bin/bash set -euo pipefail file="$(fd 'VmMetadataUpdateInterceptor\.java' | head -n1)" echo "Target: $file" echo "== method signatures ==" rg -n 'submitMetadataUpdateGCJob|submitUpdateVmInstanceMetadataGC' "$file" echo "== suspicious body (Line 139-150) ==" nl -ba "$file" | sed -n '139,150p' echo "== class fields declaring self/msg (should be empty) ==" rg -n '^\s*(private|protected|public)\s+[^;]*\b(self|msg)\b\s*;' "$file" || true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java` around lines 132 - 151, The method submitUpdateVmInstanceMetadataGC currently uses undefined symbols self and msg and has no parameter to receive vmInstanceUuid; change its signature to accept String vmInstanceUuid and update the caller submitMetadataUpdateGCJob to pass through the vmInstanceUuid argument, then inside submitUpdateVmInstanceMetadataGC use that vmInstanceUuid to set gc.vmInstanceUuid, build gc.NAME (replace msg.getVmInstanceUuid() with the vmInstanceUuid), and pass it to bus.makeTargetServiceIdByResourceUuid; remove any references to self or msg from the method body so the code compiles.
112-130:⚠️ Potential issue | 🟠 Major请改用统一的 VM UUID 解析机制,当前反射回退可能漏更新或误更新。
这里直接反射
getVmInstanceUuid(),失败再回退getResourceUuid(),对“资源 UUID ≠ VM UUID”场景不安全,也无法覆盖多 VM 影响场景。建议复用元数据模块已有的 VM UUID resolver registry 统一解析,避免把非 VM 资源当成 VM 触发 GC。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java` around lines 112 - 130, 当前的 extractVmInstanceUuid 方法通过反射尝试 getVmInstanceUuid()/getResourceUuid() 会把非 VM 资源误当成 VM;请改为调用元数据模块提供的统一 VM UUID 解析器注册表(即 VM UUID resolver registry / metadata module 的现有 resolver),替换掉直接反射逻辑:在 VmMetadataUpdateInterceptor.extractVmInstanceUuid 中使用该 registry 的解析接口来解析消息的 VM UUID(若解析器返回空或表明不是 VM 则返回 null),并正确处理解析器可能返回的多个 VM UUID 情况(若需要,调整返回调用方或只返回单个确定的 VM UUID),确保只有真实 VM 被识别触发后续逻辑。header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy (1)
10-23:⚠️ Potential issue | 🟡 Minor事件文档描述仍是占位值,建议补全。
Line [13] 的
desc "null"和 Line [20] 的空描述会直接进入对外文档,建议替换为明确语义说明。📝 建议修改
ref { name "inventory" path "org.zstack.header.storage.primary.APIRegisterVmInstanceEvent.inventory" - desc "null" + desc "注册成功后返回的虚拟机信息" type "VmInstanceInventory" since "4.10.0" clz VmInstanceInventory.class } field { name "success" - desc "" + desc "操作是否成功" type "boolean" since "4.10.0" }Based on learnings: 在ZStack项目中,代码注释应该准确反映实际实现。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy` around lines 10 - 23, The event documentation contains placeholder/missing descriptions: replace the ref block's desc "null" for inventory (org.zstack.header.storage.primary.APIRegisterVmInstanceEvent.inventory / VmInstanceInventory) with a clear Chinese description of the inventory being returned (e.g., 表示已注册的虚拟机实例信息), and add a concise Chinese desc for the field "success" (boolean) describing it indicates whether the registration succeeded; update the desc values in APIRegisterVmInstanceEventDoc_zh_cn.groovy accordingly.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3801-3824:⚠️ Potential issue | 🔴 Critical该分支存在空指针风险且未通过
completion回调结果。Line [3802] 可能返回
null,Line [3806] 会直接触发 NPE。
另外该方法签名提供了completion,但 Line [3814]-Line [3823] 仅bus.reply(msg, reply),未completion.success/fail,调用方可能一直等待。
并且 Line [3819] 的vm[uuid=%s]实际填的是主存储 UUID,不准确。🔧 建议修复
`@Override` void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue(); + if (StringUtils.isBlank(installPath)) { + completion.fail(operr("cannot find root volume[uuid:%s] installPath", msg.getRootVolumeUuid())); + return; + } String path = installPath.replaceFirst("^(.+/vol-[^/]+/).*$", "$1"); String metadataPath = String.format("%s%s", path, VM_META_SUFFIX); UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); umsg.setMetadata(msg.getMetadata()); umsg.setMetadataPath(metadataPath); umsg.setHostUuid(hostUuid); bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); bus.send(umsg, new CloudBusCallBack(msg) { `@Override` public void run(MessageReply r) { - UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); if (!r.isSuccess()) { - reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) - .withCause(r.getError())); + completion.fail(Platform.operr("failed to update vm metadata for rootVolume[uuid=%s] on host[uuid=%s]", + msg.getRootVolumeUuid(), hostUuid).withCause(r.getError())); + return; } - bus.reply(msg, reply); + completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 3801 - 3824, handle may NPE if installPath is null and never completes the provided completion; add a null-check after fetching installPath (variable installPath) and if null create an error (use Platform.operr) and call completion.fail(error) and bus.reply(msg, reply) as appropriate; keep computing metadataPath and sending UpdateVmInstanceMetadataOnHypervisorMsg as before, but in the CloudBusCallBack you must invoke completion.success(reply) on success and completion.fail(...) on failure (use UpdateVmInstanceMetadataOnPrimaryStorageReply and pass the error), and fix the error message construction to reference the correct resource id (use msg.getRootVolumeUuid() or the VM identifier from msg instead of self.getUuid()) so the logged uuid is accurate.header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java (1)
17-18:⚠️ Potential issue | 🟡 Minor补全
__example__必填示例并显式声明metadataPath约束。当前
__example__返回空消息,生成的 API 文档示例不完整;同时metadataPath建议显式写出required = true,减少约束语义歧义。🛠️ 建议修改
- `@APIParam`() + `@APIParam`(required = true) private String metadataPath; @@ public static APIRegisterVmInstanceMsg __example__() { APIRegisterVmInstanceMsg msg = new APIRegisterVmInstanceMsg(); + msg.setMetadataPath("/zstack_ps/metadata/vm-xxxx/meta.json"); + msg.setPrimaryStorageUuid(uuid()); + msg.setClusterUuid(uuid()); return msg; }As per coding guidelines,
API 类需要实现 __example__ 方法以便生成 API 文档,并确保生成对应的 Groovy API Template 与 API Markdown 文件。Also applies to: 59-62
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java` around lines 17 - 18, The API class APIRegisterVmInstanceMsg lacks a populated __example__ and metadataPath should explicitly declare required=true; implement or update the __example__ method to return a realistic example APIRegisterVmInstanceMsg instance with metadataPath populated (and any other required fields) so the Groovy API template/Markdown can be generated, and change the field annotation on metadataPath to `@APIParam`(required = true) to make the constraint explicit.plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java (1)
4720-4734:⚠️ Potential issue | 🟠 Major为元数据字段补充日志脱敏注解。
Line [4721] 和 Line [4733] 的
metadata直接暴露在命令/响应对象中,未加@NoLogging会带来敏感信息泄露风险。🔧 建议修复
public static class WriteVmInstanceMetadataCmd extends AgentCommand { + `@NoLogging` public String metadata; public String metadataPath; } @@ public static class ReadVmInstanceMetadataRsp extends AgentResponse { + `@NoLogging` public String metadata; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines 4720 - 4734, The metadata fields are logged without redaction in WriteVmInstanceMetadataCmd.metadata and ReadVmInstanceMetadataRsp.metadata; annotate both fields with `@NoLogging` to prevent sensitive metadata from being emitted to logs, i.e., add the `@NoLogging` annotation above the metadata field declarations in the WriteVmInstanceMetadataCmd and ReadVmInstanceMetadataRsp classes and import the `@NoLogging` annotation if needed.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2052-2067:⚠️ Potential issue | 🔴 Critical回调完成路径与回复类型错误,会导致调用方无法正确收敛。
Line [2060] 使用了错误的 reply 类型;Line [2065] 走
bus.reply而非completion,与方法契约不一致,容易造成上层等待超时。🔧 建议修复
public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); umsg.setMetadata(msg.getMetadata()); umsg.setHostUuid(hostUuid); bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); - bus.send(umsg, new CloudBusCallBack(msg) { + bus.send(umsg, new CloudBusCallBack(completion) { `@Override` public void run(MessageReply r) { - RevertVolumeFromSnapshotOnPrimaryStorageReply reply = new RevertVolumeFromSnapshotOnPrimaryStorageReply(); if (!r.isSuccess()) { - reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", msg.getPrimaryStorageUuid()) - .withCause(r.getError())); + completion.fail(Platform.operr("failed to update vm metadata for root volume[uuid=%s] on hypervisor.", + msg.getRootVolumeUuid()).withCause(r.getError())); + return; } - bus.reply(msg, reply); + completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); } }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java` around lines 2052 - 2067, In handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...) replace the incorrect RevertVolumeFromSnapshotOnPrimaryStorageReply and bus.reply usage: construct an UpdateVmInstanceMetadataOnPrimaryStorageReply, and in the CloudBusCallBack's run() call the ReturnValueCompletion by invoking completion.fail(...) with the Platform.operr(...) when r.isSuccess() is false, and call completion.success(reply) when r.isSuccess() is true; remove the bus.reply(msg, ...) call so the method fulfills the ReturnValueCompletion contract instead of sending a bus reply.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1938-1951:⚠️ Potential issue | 🟠 Majorbackend 选择与空值校验缺失,存在 NPE 风险。
getUsableBackend()可能返回 null(参见 Line 688-706 的实现),但此处没有空值检查。当无可用 backend 时,Line 1940 调用backend.handle(...)会抛出 NPE。建议参考
getBackendByHostUuid(hostUuid)方法(已在前序逻辑中获取了 hostUuid),以确保选择正确的 hypervisor backend,同时添加 null 检查。,
🔧 建议修复
- final NfsPrimaryStorageBackend backend = getUsableBackend(); + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); + if (backend == null) { + reply.setError(operr("no backend found for host[uuid:%s]", hostUuid)); + bus.reply(msg, reply); + return; + } backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java` around lines 1938 - 1951, getUsableBackend() can return null causing a NPE when calling backend.handle(...); change the logic to select the backend via getBackendByHostUuid(hostUuid) (which uses the already-known hostUuid) and add a null-check for the resolved backend before calling handle: if backend is null, set reply.setError(...) with an appropriate error code and bus.reply(msg, reply) and return; otherwise call backend.handle(msg, hostUuid, ...). Reference symbols: getUsableBackend(), getBackendByHostUuid(hostUuid), backend.handle(...), reply, bus.reply(...).header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java (1)
9-16:⚠️ Potential issue | 🟠 MajorGET 路径缺少
{uuid}占位符,与必填参数定义不一致。
uuid是必填字段(Line 15-16),但 Line 10 的 path 没有包含资源变量绑定,导致 REST 路由与参数定义不匹配。,
🔧 建议修复
`@RestRequest`( - path = "/primary-storage/vm-instances/metadata", + path = "/primary-storage/{uuid}/vm-instances/metadata", method = HttpMethod.GET, responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class )根据编码规范:"当 path 中引用消息类变量时,使用
{variableName}格式。"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java` around lines 9 - 16, The REST path in APIGetVmInstanceMetadataFromPrimaryStorageMsg does not include the required {uuid} placeholder while the message defines uuid as a required `@APIParam`; update the `@RestRequest` path value to include the path variable (e.g., "/primary-storage/vm-instances/metadata/{uuid}") so the route binds to the uuid field and matches the APISyncCallMessage's required parameter.plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java (1)
7382-7387:⚠️ Potential issue | 🟡 Minor失败分支仍写入
metadata,会污染错误响应。Line [7383]-Line [7387] 在
rsp.isSuccess()为false时仍执行reply.setMetadata(rsp.metadata),调用方可能在失败响应中读到无效元数据。建议修复
public void success(ReadVmInstanceMetadataRsp rsp) { if (!rsp.isSuccess()) { reply.setError(operr("operation error, because:%s", rsp.getError())); + } else { + reply.setMetadata(rsp.metadata); } - reply.setMetadata(rsp.metadata); bus.reply(msg, reply); completion.done(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java` around lines 7382 - 7387, The success(ReadVmInstanceMetadataRsp rsp) callback currently sets reply.setMetadata(rsp.metadata) even when rsp.isSuccess() is false, contaminating error responses; change the logic in the success method so that if !rsp.isSuccess() you set the error on reply (reply.setError(...)) and immediately bus.reply(msg, reply) (or return) without calling reply.setMetadata, otherwise (else) set reply.setMetadata(rsp.metadata) and then bus.reply(msg, reply); ensure the unique symbols involved are ReadVmInstanceMetadataRsp, success(...), reply.setError, reply.setMetadata, bus.reply, msg and reply so metadata is only attached on successful responses.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (2)
1851-1855:⚠️ Potential issue | 🟠 Major失败分支未设置 event 错误。
当
registerVmInstance失败时,fail回调直接 publish event 但没有设置 error,调用方会误认为操作成功。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 1851 - 1855, In the fail(ErrorCode errorCode) callback inside PrimaryStorageBase (the anonymous callback handling registerVmInstance), set the event error and mark it failed before publishing so callers don't treat it as success; specifically call event.setError(errorCode) (and if available event.setSuccess(false)) then publish the event and call chain.next() (retain chain.next() order) to ensure the failure is propagated correctly.
2185-2188:⚠️ Potential issue | 🔴 Critical
GetVmInstanceMetadataFromPrimaryStorageMsg未在handleLocalMessage中注册,消息将无法被处理。Line 2185 创建并发送
GetVmInstanceMetadataFromPrimaryStorageMsg,但handleLocalMessage(...)方法中缺少对应的instanceof分支,消息会落入bus.dealWithUnknownMessage(msg)。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 2185 - 2188, handleLocalMessage is missing an instanceof branch for GetVmInstanceMetadataFromPrimaryStorageMsg so those messages fall through to bus.dealWithUnknownMessage; add an instanceof check for GetVmInstanceMetadataFromPrimaryStorageMsg in handleLocalMessage and route it to the same handling flow used for similar messages (e.g., call an existing handler method or implement a new private method like handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) that processes the request and sends a reply), ensuring you use the message's primary storage UUID and follow the existing response/error patterns used by other branches.compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java (3)
30-32:⚠️ Potential issue | 🟡 Minor
isSensitiveField缺少空值保护。Line 30-32 在
key == null时会抛出 NPE,建议先做空值判断后再匹配正则。🔧 建议修复
public static boolean isSensitiveField(String key) { + if (StringUtils.isEmpty(key)) { + return false; + } return SENSITIVE_PATTERN.matcher(key).find(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 30 - 32, The isSensitiveField method can throw NPE when key is null; update the isSensitiveField(String key) implementation to first check for null (and optionally empty) and return false if so, before calling SENSITIVE_PATTERN.matcher(key).find(); keep the existing SENSITIVE_PATTERN reference and method signature so callers are unchanged.
19-24:⚠️ Potential issue | 🔴 Critical
SENSITIVE_TAGS未初始化,静态初始化块不可用。Line 19 声明了
final列表却未赋值,Line 23 立即使用,会导致类初始化失败。请先完成初始化,再构建正则。🔧 建议修复
+import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; ... - private static final List<String> SENSITIVE_TAGS ; + private static final List<String> SENSITIVE_TAGS = Arrays.asList( + "password", "secret", "token", "credential", "apikey" + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 19 - 24, SENSITIVE_TAGS 被声明为 final 但未赋值,导致静态初始化块在构建 SENSITIVE_PATTERN 时抛出错误;在使用 SENSITIVE_TAGS 构建正则之前为其赋初值(例如在声明处或静态块顶部用 Arrays.asList(...) / List.of(...) 并用 Collections.unmodifiableList(...) 包装),然后再基于该已初始化的列表构造 regex 并编译到 SENSITIVE_PATTERN;同时建议对列表元素做 Pattern.quote 转义并处理空列表情况以避免生成无效正则。(参见标识符:SENSITIVE_TAGS、SENSITIVE_PATTERN、静态初始化块)
4-8:⚠️ Potential issue | 🔴 Critical存在未解析符号,当前代码无法编译。
Line 17/42/44/67 使用了未导入(或未定义)的符号:
GlobalConfig、VM_METADATA_SENSITIVE_KEY、AESCipher、AlertManager、SystemTagDTO。请补齐正确 import,并确认VM_METADATA_SENSITIVE_KEY在当前分支确实存在且可见。Also applies to: 17-17, 42-42, 44-44, 67-67
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.java` around lines 4 - 8, SensitiveFieldHandler references unresolved symbols: GlobalConfig, VM_METADATA_SENSITIVE_KEY, AESCipher, AlertManager, and SystemTagDTO; add the proper imports for these symbols in SensitiveFieldHandler (import the GlobalConfig, AESCipher, AlertManager and SystemTagDTO classes from their correct packages in the codebase) and ensure the VM_METADATA_SENSITIVE_KEY constant is present and accessible in this branch (if it lives in another class or interface, import that class or qualify the reference). After adding imports, rebuild to confirm compilation and visibility of VM_METADATA_SENSITIVE_KEY in SensitiveFieldHandler.compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java (1)
3-4:⚠️ Potential issue | 🔴 Critical
ResourceConfigDTO目前是空壳,元数据配置无法落地。Line 3-4 仅定义空类,无法表达资源配置键值与敏感标识,后续转换/序列化链路会出现数据绑定失败或方法缺失问题。建议至少补齐配置键、配置值、敏感标记及对应访问器。
🔧 建议补齐最小 DTO 结构
public class ResourceConfigDTO { + private String configKey; + private String configValue; + private boolean isSensitive; + + public String getConfigKey() { + return configKey; + } + + public void setConfigKey(String configKey) { + this.configKey = configKey; + } + + public String getConfigValue() { + return configValue; + } + + public void setConfigValue(String configValue) { + this.configValue = configValue; + } + + public boolean isSensitive() { + return isSensitive; + } + + public void setIsSensitive(boolean sensitive) { + isSensitive = sensitive; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.java` around lines 3 - 4, ResourceConfigDTO is an empty placeholder so metadata configs cannot be serialized or bound; add a minimal DTO shape by defining fields (e.g., String key, String value, boolean sensitive), provide getters/setters for each field (getKey/setKey, getValue/setValue, isSensitive/setSensitive), add a no-arg constructor and a parameterized constructor for convenience, and implement equals/hashCode (and optional toString) to ensure proper binding and usage in conversion/serialization paths; update any usages expecting these accessors to compile against ResourceConfigDTO.compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java (1)
6-53:⚠️ Potential issue | 🔴 CriticalDTO 关键字段被定义为
static,且primaryStorageUuid未声明。Line 6-26/31-205 把元数据字段和访问器做成
static,会导致所有实例共享状态;Line 47-53 又引用了未声明的primaryStorageUuid,会直接编译失败。建议改为实例字段与实例访问器,并补齐primaryStorageUuid字段。🔧 修复方向(示例)
public class VolumeMetadataDTO { - public static String description; - public static String name; + private String primaryStorageUuid; + private String description; + private String name; ... - public static String getDescription() { + public String getDescription() { return description; } - public static void setDescription(String description) { - VolumeMetadataDTO.description = description; + public void setDescription(String description) { + this.description = description; } ... - public static String getPrimaryStorageUuid() { + public String getPrimaryStorageUuid() { return primaryStorageUuid; } - public static void setPrimaryStorageUuid(String primaryStorageUuid) { - VolumeMetadataDTO.primaryStorageUuid = primaryStorageUuid; + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.java` around lines 6 - 53, The DTO currently declares metadata fields as static and references an undeclared primaryStorageUuid, causing shared state and a compile error; change all metadata fields (e.g., description, name, vmInstanceUuid, rootImageUuid, diskOfferingUuid, format, installPath, type, size, actualSize, deviceId, state, status, createDate, lastOpDate, lastDetachDate, lastAttachDate, lastVmInstanceUuid, isShareable, volumeQos, protocol) to instance (non-static) fields, convert their getters/setters to instance methods (remove static on methods like getDescription/setDescription/getName/setName), add a new instance field primaryStorageUuid plus instance getPrimaryStorageUuid and setPrimaryStorageUuid methods, and ensure systemTags and resourceConfigs remain instance fields; update any references elsewhere to use instance accessors.compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java (1)
344-349:⚠️ Potential issue | 🟡 Minor指数退避首轮延迟偏大一档。
Line 344 先递增再计算指数,首轮会变成 20s,而不是注释里的 10s。建议用递增前计数计算指数,或改为
retryCount - 1。🔧 建议修复
private void onUpdateFail(GCCompletion completion, ErrorCode err) { retryCount++; - int exponent = Math.min(retryCount, MAX_EXPONENT); + int exponent = Math.min(Math.max(retryCount - 1, 0), MAX_EXPONENT); NEXT_TIME = BASE_DELAY_SECONDS * (1L << exponent); NEXT_TIME_UNIT = TimeUnit.SECONDS;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java` around lines 344 - 349, The code increments retryCount before computing the exponential backoff, causing the first retry to use 2^1 (20s) instead of 2^0 (10s); change the exponent calculation to use retryCount - 1 (clamped to >=0) so the first retry uses BASE_DELAY_SECONDS * 2^0. Update the computation around retryCount, MAX_EXPONENT, NEXT_TIME and BASE_DELAY_SECONDS (or move the retryCount++ after computing NEXT_TIME) to ensure exponent = Math.min(Math.max(retryCount - 1, 0), MAX_EXPONENT) is used and NEXT_TIME_UNIT remains TimeUnit.SECONDS.compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.java (1)
7-42:⚠️ Potential issue | 🔴 Critical
VmMetadataDTO对外访问契约不完整。Line 7-42 只有私有字段,没有对应 getter/setter;同时实现了
Serializable但未定义serialVersionUID。这会让转换器/序列化链路难以稳定读写该 DTO。建议补齐所有字段访问器并增加serialVersionUID。🔧 建议补齐基础结构(示例)
public class VmMetadataDTO implements Serializable { + private static final long serialVersionUID = 1L; + private String schemaVersion; private Long createTime; ... + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public Long getCreateTime() { + return createTime; + } + + public void setCreateTime(Long createTime) { + this.createTime = createTime; + } + // 其余字段同样补齐 getter/setter }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.java` around lines 7 - 42, VmMetadataDTO currently exposes only private fields and implements Serializable without a serialVersionUID; add a private static final long serialVersionUID (pick a stable constant) and implement public getters and setters for every field (e.g., schemaVersion, createTime, name, description, hypervisorType, guestOsType, architecture, platform, type, rootVolumeUuid, imageUuid, memorySize, reservedMemorySize, cpuNum, cpuSpeed, volumeList, nicList, snapshotList, snapshotGroupList, systemTags, resourceConfigs) so external code and serializers can reliably access and persist the DTO; keep standard JavaBean signatures on the VmMetadataDTO class.compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java (4)
9387-9390:⚠️ Potential issue | 🟠 Major根卷查询缺少空值兜底,存在 NPE 风险。
findTuple()可能返回null,Line 9389-9390 直接tuple.get(...)会抛异常,且调用方收不到明确错误。🛠️ 建议修复
Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid) .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple(); + if (tuple == null) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("cannot find root volume for vm[uuid:%s]", msg.getUuid())); + bus.reply(msg, reply); + return; + } String primaryStorageUuid = tuple.get(0, String.class); String rootVolumeUuid = tuple.get(1, String.class);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9387 - 9390, The query using Q.New(VolumeVO.class).select(...).eq(...).findTuple() can return null causing an NPE when calling tuple.get(...); update the code around the Tuple tuple = ... findTuple() call to check for tuple == null and handle it explicitly (e.g. throw a clear runtime/CloudRuntimeException or return a structured error) before accessing VolumeVO_.primaryStorageUuid/VolumeVO_.uuid via tuple.get(...), so callers receive a meaningful error instead of an NPE.
9491-9511:⚠️ Potential issue | 🟠 Major元数据序列化出口不统一,敏感字段保护链路可能被绕过。
当前实际返回的是
JSONObjectUtil.toJsonString(vmMetadata),同时getResourceSystemTagFromDb/getResourceConfigFromDb直接序列化 VO 全量内容;这会绕过统一 DTO + Serializer 的脱敏/加密路径。建议统一只走一个序列化出口。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9491 - 9511, The getResourceSystemTagFromDb and getResourceConfigFromDb methods directly call JSONObjectUtil.toJsonString on VO objects, bypassing the VM metadata DTO/serializer and its sensitive-field protections; change these methods to convert each VO to the corresponding DTO (or use the existing VmMetadataSerializer path) and return serialized strings via the same DTO serialization routine used by VmMetadataSerializer.serialize(vmDto) so all outputs go through the unified serializer/desensitization pipeline (update getResourceSystemTagFromDb, getResourceConfigFromDb to use converter methods or the serializer instead of serializing VO instances directly).
9402-9410:⚠️ Potential issue | 🟠 Major失败分支未回复消息,会导致调用方悬挂。
失败路径里只设置错误并提交 GC,没有
bus.reply(msg, reply)。🛠️ 建议修复
if (!r.isSuccess()) { reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) .withCause(r.getError())); - + bus.reply(msg, reply); submitUpdateVmInstanceMetadataGC(); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9402 - 9410, In the failure branch inside VmInstanceBase where r.isSuccess() == false, you set reply.setError(...) and call submitUpdateVmInstanceMetadataGC() but never send the reply; add a call to bus.reply(msg, reply) in that branch (e.g., after setting the error and before returning) so the caller receives the error. Ensure the change touches the block containing r.isSuccess(), reply.setError(...), submitUpdateVmInstanceMetadataGC(), and bus.reply(msg, reply).
9486-9495:⚠️ Potential issue | 🔴 Critical残留 DTO 代码包含未定义符号,当前代码无法编译。
vmUuid/volumeVos/snapshotVos/groupVos/tagVos/configVos/snapshotDto未定义;该段会直接导致编译失败。🧹 若此段为残留代码,建议先移除
- VmInstanceVO vmVo = dbf.findByUuid(vmUuid, VmInstanceVO.class); - List<VmNicVO> nicVos = dbf.listBySql("select * from VmNicVO where vmInstanceUuid = ?", VmNicVO.class, vmUuid); - - VmMetadataDTO vmDto = VmMetadataConverter.convert(vmVo, nicVos, volumeVos, snapshotVos, groupVos, tagVos, configVos); - - String json = VmMetadataSerializer.serialize(vmDto); - - // - VolumeSnapshotReferenceVO refVo = dbf.findByUuid(snapshotDto.getSnapshotUuid(), VolumeSnapshotReferenceVO.class); - VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9486 - 9495, This block contains residual DTO usage that references undefined symbols (vmUuid, volumeVos, snapshotVos, groupVos, tagVos, configVos, snapshotDto) and causes compilation failure; either remove the entire snippet that constructs VmMetadataDTO/serializes it and the subsequent VolumeSnapshotReferenceVO lookup and VmMetadataConverter.supplementSnapshotReference call, or replace it with the correct, in-scope variables and context where those values are defined (ensure you use the method's actual vmUuid/volume/snapshot/group/tag/config variables and a valid snapshotDto before calling VmMetadataConverter.convert, VmMetadataSerializer.serialize, and supplementSnapshotReference).compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java (2)
1-4:⚠️ Potential issue | 🔴 Critical删除重复的 package 声明,修复编译错误
文件包含两个
package声明(第 1 行和第 3 行),这将导致 Java 编译失败。必须删除第 3 行的package com.zstack.core.vmmetadata;。此外,代码引用了多个未导入的类和常量,包括
GlobalConfig、VM_METADATA_DEFAULT_VERSION(第 50 行)、CoreMemorySnapshotConfigs(第 154 行)、VolumeSnapshotGroupRefVO(第 135 行)、VolumeSnapshotReferenceVO(第 192 行)和VolumeSnapshotReferenceTreeVO(第 203 行)。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 1 - 4, Remove the duplicate package declaration (delete the second line declaring package com.zstack.core.vmmetadata) so there is only a single valid package declaration at the top of VmMetadataConverter.java, and add the missing imports for the referenced symbols: GlobalConfig, VM_METADATA_DEFAULT_VERSION, CoreMemorySnapshotConfigs, VolumeSnapshotGroupRefVO, VolumeSnapshotReferenceVO, and VolumeSnapshotReferenceTreeVO (ensure the import statements use the correct packages where those types/constants are defined) so the compiler can resolve those references used in methods within the VmMetadataConverter class.
40-43:⚠️ Potential issue | 🟠 Major对输入集合添加空值防护,避免 NPE
convertVmNic、convertVolume、convertSnapshot、convertSnapshotGroup方法直接对入参调用stream(),若调用方传入null,会触发NullPointerException。建议使用统一的空集合兜底策略。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.java` around lines 40 - 43, 在 VmMetadataConverter 中,dto.setNicList/ setVolumeList/ setSnapshotList/ setSnapshotGroupList 调用的 convertVmNic、convertVolume、convertSnapshot、convertSnapshotGroup 目前直接对入参调用 stream(),若入参为 null 会抛出 NPE;请在这些方法(convertVmNic, convertVolume, convertSnapshot, convertSnapshotGroup)中添加统一空集合兜底逻辑:当入参为 null 时返回 Collections.emptyList()(或等价的空列表),否则按当前逻辑处理并返回结果;确保所有调用点(例如 dto.setNicList(...) 等)无需额外判空即可安全使用。compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java (2)
175-194:⚠️ Potential issue | 🔴 CriticalPlexus
@Component与手工实例化导致依赖注入失败,运行时将触发 NPE
VmUuidResolverRegistry使用的是 Plexus 的@Component(第 3 行导入),而非 Spring 的@Component,Spring 不会将其作为 Bean 管理。同时,VolumeBasedVmUuidResolver和ResourceBasedVmUuidResolver在init()方法中通过new手工实例化(第 183、189 行),这些解析器依赖@Autowired private DatabaseFacade dbf,但通过new创建的实例无法获得 Spring 注入,dbf将为null,导致运行时 NPE。此外,文件还缺少必要的导入:
java.util.Map、java.util.HashMap、javax.annotation.PostConstruct,会导致编译失败。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java` around lines 175 - 194, VmUuidResolverRegistry is annotated with Plexus `@Component` and manually new()s VolumeBasedVmUuidResolver and ResourceBasedVmUuidResolver in init(), so their `@Autowired` DatabaseFacade (dbf) won't be injected and will cause NPEs; also missing imports for Map, HashMap, and PostConstruct. Fix by replacing the Plexus `@Component` with Spring's org.springframework.stereotype.Component (or add Spring bean registration), stop instantiating resolvers with new in VmUuidResolverRegistry.init() and instead inject them as Spring beans (e.g., `@Autowired` fields or a List<VmUuidResolver>/ApplicationContext.getBean(...) lookup) so VolumeBasedVmUuidResolver and ResourceBasedVmUuidResolver receive their DatabaseFacade; ensure the class declares/initializes the resolvers map and defaultResolver as before and add imports for java.util.Map, java.util.HashMap, and javax.annotation.PostConstruct.
1-14:⚠️ Potential issue | 🔴 Critical代码使用不存在的类型,且缺少多个必要导入,编译将失败
存在两类问题需要修正:
1. 不存在的类型(必须更正):
APICreateVolumesSnapshotMsg(第 88、184 行):此类在代码库中不存在。应改为CreateVolumesSnapshotMsg(位于org.zstack.header.storage.snapshot包)。TagVO(第 117 行):此类不存在。应根据业务需求改为SystemTagVO或UserTagVO(位于org.zstack.header.tag包)。2. 缺少的必要导入:
java.util.Map、java.util.HashMap(第 177 行)javax.annotation.PostConstruct(第 180 行)org.zstack.header.vm.VmInstanceMessage(第 53 行)org.zstack.header.volume.VolumeMessage、APICreateVolumeSnapshotGroupMsg、APIAttachDataVolumeToVmMsg、APIDetachDataVolumeFromVmMsg、VolumeVO(第 90、139、186、187 行)org.zstack.header.vm.VmNicVO(第 148 行)org.zstack.header.storage.snapshot.VolumeSnapshotVO(第 157 行)org.zstack.core.db.SQL(第 78 行)org.zstack.header.tag.APIAbstractCreateTagMsg、APIDeleteTagMsg、APICreateSystemTagMsg、APIUpdateSystemTagMsg(第 112-192 行)org.zstack.resourceconfig.APIUpdateResourceConfigMsg、APIDeleteResourceConfigMsg(第 193、194 行)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.java` around lines 1 - 14, The file uses non-existent types and misses multiple imports causing compile errors: replace APICreateVolumesSnapshotMsg with org.zstack.header.storage.snapshot.CreateVolumesSnapshotMsg and replace TagVO with org.zstack.header.tag.SystemTagVO (or UserTagVO if business requires System vs User tags), then add the missing imports: java.util.Map, java.util.HashMap, javax.annotation.PostConstruct, org.zstack.header.vm.VmInstanceMessage, org.zstack.header.volume.VolumeMessage, org.zstack.header.storage.snapshot.VolumeSnapshotVO, org.zstack.header.storage.snapshot.CreateVolumesSnapshotMsg (as above), org.zstack.header.volume.VolumeVO, org.zstack.header.volume.APICreateVolumeSnapshotGroupMsg, org.zstack.header.volume.APIAttachDataVolumeToVmMsg, org.zstack.header.volume.APIDetachDataVolumeFromVmMsg, org.zstack.header.vm.VmNicVO, org.zstack.core.db.SQL, and tag-related messages org.zstack.header.tag.APIAbstractCreateTagMsg, org.zstack.header.tag.APIDeleteTagMsg, org.zstack.header.tag.APICreateSystemTagMsg, org.zstack.header.tag.APIUpdateSystemTagMsg, plus org.zstack.resourceconfig.APIUpdateResourceConfigMsg and org.zstack.resourceconfig.APIDeleteResourceConfigMsg; update import section and type references in methods/fields (e.g., any usage of APICreateVolumesSnapshotMsg, TagVO, VolumeVO, VmNicVO, VolumeSnapshotVO) and add `@PostConstruct` where initialization expects it.
🧹 Nitpick comments (9)
header/src/main/java/org/zstack/header/vm/VmMetadata.java (1)
8-8: 建议为VmMetadata增加类级 Javadoc 说明数据语义。该类字段大量承载
*.toString结果,建议在类注释中明确“这是元数据传输载体而非领域对象”,减少后续误用。As per coding guidelines: “代码应尽量做到自解释”。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/VmMetadata.java` at line 8, 为 VmMetadata 类添加类级 Javadoc,明确说明该类是一个元数据传输载体(DTO),其字段主要承载对象的 toString() 结果而非完整领域对象,提醒读者不要将其当作领域模型进行业务逻辑或持久化使用,并注明字段语义、预期格式和可变性(例如哪些字段可能为 null 或仅为字符串表示),以避免后续误用。header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java (1)
3-9: 接口命名与职责语义不一致,且缺少方法注释。当前更像“注册流程扩展点/策略”,
Exception命名容易误导;同时接口方法建议补充 Javadoc,明确输入路径与返回路径约束。As per coding guidelines "命名应尽量用完整的单词组合表达意图" 与 "接口方法...必须配有有效的 Javadoc 注释"。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java` around lines 3 - 9, Rename the misleading interface RegisterVmInstanceException to a name that reflects its role (e.g., RegisterVmInstanceExtension or VmInstanceRegistrationStrategy) and update all references; then add Javadoc to the interface and each method—documenting parameters and return semantics for updateVolumeInstallPath(String installPath) and updateVolumeSnapshotInstallPath(String installPath) (e.g., expected input format, whether null is allowed, and whether return is a transformed/normalized install path or original) and clarify getPrimaryStorageType()'s contract (must not return null, represents the storage type this implementation handles). Ensure the new name and Javadoc follow project naming and documentation guidelines.header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java (2)
22-25:__example__()方法应提供有意义的示例数据。当前
__example__()返回空的 reply 对象,没有设置vmInstanceMetadata的示例值。API 类需要实现__example__方法以便生成 API 文档,建议添加示例数据。♻️ 建议修复
public static APIGetVmInstanceMetadataFromPrimaryStorageReply __example__() { APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply(); + reply.setVmInstanceMetadata(java.util.Arrays.asList("/path/to/vm/metadata1", "/path/to/vm/metadata2")); return reply; }根据编码规范:"API 类需要实现
__example__方法以便生成 API 文档"。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java` around lines 22 - 25, The __example__() in APIGetVmInstanceMetadataFromPrimaryStorageReply returns an empty reply; update it to populate vmInstanceMetadata with a meaningful example instance (create and set a VmInstanceMetadata object with representative fields such as uuid, name, disk and network metadata or other relevant properties), set it on the reply via setVmInstanceMetadata(...), and return the reply; target the APIGetVmInstanceMetadataFromPrimaryStorageReply.__example__ method and the VmInstanceMetadata class/fields when adding the example data so API docs show realistic sample values.
10-12:@RestResponse(allTo = "all")与字段名不一致。
allTo参数值为"all",但实际字段名为vmInstanceMetadata。参考其他 API Reply 的实现(如APIRegisterVmInstanceReply使用allTo = "inventory"对应字段inventory),建议保持一致性。♻️ 建议修复
-@RestResponse(allTo = "all") +@RestResponse(allTo = "vmInstanceMetadata") public class APIGetVmInstanceMetadataFromPrimaryStorageReply extends APIReply {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java` around lines 10 - 12, The `@RestResponse`(allTo = "all") annotation on class APIGetVmInstanceMetadataFromPrimaryStorageReply does not match the reply field name vmInstanceMetadata; update the annotation to use allTo = "vmInstanceMetadata" (or rename the field to match the existing allTo value) so the annotation value and the field name in APIGetVmInstanceMetadataFromPrimaryStorageReply are consistent (adjust any serializers/tests if you rename the field).storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (2)
1958-1964: 建议使用computeIfAbsent简化代码。当前的 if-else 逻辑可以用
Map.computeIfAbsent简化,提高可读性。♻️ 建议重构
- if (snapshotsByTreeUuid.containsKey(inv.getTreeUuid())) { - snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); - } else { - snapshotsByTreeUuid.put(inv.getTreeUuid(), new ArrayList<>()); - snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); - } + snapshotsByTreeUuid.computeIfAbsent(inv.getTreeUuid(), k -> new ArrayList<>()).add(inv);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 1958 - 1964, Replace the manual if-else that checks snapshotsByTreeUuid.containsKey(inv.getTreeUuid()) and then creates a new ArrayList or adds to the existing list with a single computeIfAbsent call: use snapshotsByTreeUuid.computeIfAbsent(inv.getTreeUuid(), k -> new ArrayList<>()).add(inv) so the map entry is created if missing and the snapshot (inv) is added in one concise operation; update the lambda block where inv is processed to use this pattern (references: snapshotsByTreeUuid, inv, getTreeUuid).
2065-2113: 移除或实现注释掉的代码块。Lines 2065-2112 包含大量注释掉的代码。根据编码规范,注释掉的代码属于死代码,应该移除或完整实现。如果这是待办功能,建议用
TODO注释标记并移除实现代码。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java` around lines 2065 - 2113, The commented-out block handling vmSystemTags/vmResourceConfigs (references: vmSystemTags, vmResourceConfigs, getResourceSystemTagFromSystem, getResourceConfigFromSystem, SystemTagVO, ResourceConfigVO, VmInstanceVO, newVm) must not remain as dead code: either fully implement its behavior (uncomment and fix logic: parse tags, find matching SystemTag/GlobalConfig, create and populate SystemTagVO and ResourceConfigVO instances and persist or add them to the appropriate lists for the newVm) or remove the entire commented section and, if this is a planned feature, replace it with a concise TODO comment pointing to the task/issue id and a short note like "implement VM tag/config propagation for newVm" so the intent is tracked without leaving large commented blocks.compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.java (1)
14-14:NONE常量缺少文档注释。
CONFIG和STORAGE都有清晰的 Javadoc 注释说明用途,但NONE缺少对应的文档说明。建议补充注释以保持一致性。📝 建议修复
STORAGE, + /** + * 无影响:操作不会对 VM 元数据产生任何影响 + */ NONE🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.java` at line 14, Add a Javadoc comment for the enum constant NONE in MetadataImpactLevel explaining its semantics (e.g., no impact on VM metadata) to match the existing comments for CONFIG and STORAGE; edit the enum MetadataImpactLevel and add a short Javadoc above the NONE constant that clearly describes when NONE should be used, mirroring the style and tone of the existing comments for CONFIG and STORAGE.compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java (2)
84-98:compareVersion方法返回值与文档不一致。Javadoc 声明返回值为 1、0 或 -1,但实际返回的是
num1 - num2,可能产生任意整数值(如 2、-3 等)。虽然当前调用处只检查> 0,但这与文档描述不符,可能导致未来使用时出现问题。♻️ 建议修复
private static int compareVersion(String version1, String version2) { String[] v1 = version1.replace("v", "").split("\\."); String[] v2 = version2.replace("v", "").split("\\."); int len = Math.max(v1.length, v2.length); for (int i = 0; i < len; i++) { int num1 = i < v1.length ? Integer.parseInt(v1[i]) : 0; int num2 = i < v2.length ? Integer.parseInt(v2[i]) : 0; if (num1 != num2) { - return num1 - num2; + return Integer.compare(num1, num2); } } return 0; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java` around lines 84 - 98, The compareVersion method returns num1 - num2 which can yield arbitrary integers contrary to the Javadoc; update compareVersion(String version1, String version2) to return only 1, 0, or -1 by replacing the current numeric subtraction result with a ternary or conditional that returns 1 when num1 > num2, -1 when num1 < num2, and keep returning 0 when equal (preserve the existing loop and final return 0 logic that uses v1/v2 and num1/num2).
74-82:inferStorageType默认返回 "nfs" 可能不准确。当
metadataPath为null或不包含已知路径模式时,方法默认返回"nfs"。建议添加 null 检查并考虑使用更明确的默认值或抛出异常。🛡️ 建议修复
private static String inferStorageType(String metadataPath) { + if (StringUtils.isBlank(metadataPath)) { + return "unknown"; + } if (StringUtils.contains(metadataPath, "/dev/")) { return "sblk"; } else if (StringUtils.contains(metadataPath, "/vms_ds/")) { return "local"; } else { return "nfs"; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java` around lines 74 - 82, The inferStorageType method should not silently default to "nfs" when metadataPath is null or unrecognized: add a null/blank check at the top of inferStorageType(String metadataPath) and if metadataPath is null/blank throw an IllegalArgumentException (with a clear message including the offending value) instead of returning "nfs"; keep the existing path checks for "/dev/" and "/vms_ds/" and only use "nfs" for an explicit known NFS pattern or, if you prefer a non-exception route, return a clear sentinel like "unknown"—ensure callers of inferStorageType handle the new exception/ sentinel appropriately.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xml
📒 Files selected for processing (61)
compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpact.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataImpactLevel.javacompute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.javacompute/src/main/java/org/zstack/compute/vm/metadata/ResourceConfigDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SensitiveFieldHandler.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupRefMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SnapshotMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataConverter.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmNicMetadataDTO.javacompute/src/main/java/org/zstack/compute/vm/metadata/VolumeMetadataDTO.javacore/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.javacore/src/main/java/org/zstack/core/gc/SubmitTimeBasedGarbageCollectorMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.javaheader/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.javaheader/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEvent.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/VmInstanceConstant.javaheader/src/main/java/org/zstack/header/vm/VmMetadata.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMHost.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceResult.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy
✅ Files skipped from review due to trivial changes (1)
- header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy
🚧 Files skipped from review as they are similar to previous changes (20)
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
- compute/src/main/java/org/zstack/compute/vm/metadata/SnapshotGroupRefMetadataDTO.java
- compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataSerializer.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
- compute/src/main/java/org/zstack/compute/vm/metadata/VmNicMetadataDTO.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.java
- compute/src/main/java/org/zstack/compute/vm/metadata/SystemTagDTO.java
- compute/src/main/java/org/zstack/compute/vm/metadata/SnapshotMetadataDTO.java
- core/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.java
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.java
- header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- core/src/main/java/org/zstack/core/gc/SubmitTimeBasedGarbageCollectorMsg.java
| private static VmMetadataDTO upgradeV10ToV11(VmMetadataDTO v10) { | ||
| VmMetadataDTO v11 = new VmMetadataDTO(); | ||
| // 复制 v1.0 所有字段 | ||
| v11.setVmUuid(v10.getVmUuid()); | ||
| v11.setVmName(v10.getVmName()); | ||
| v11.setNumCpus(v10.getNumCpus()); | ||
| v11.setMemSize(v10.getMemSize()); | ||
| v11.setVolumeList(v10.getVolumeList()); | ||
| v11.setNicList(v10.getNicList()); | ||
| v11.setSnapshotList(v10.getSnapshotList()); | ||
| v11.setSystemTags(v10.getSystemTags()); | ||
| v11.setResourceConfigs(v10.getResourceConfigs()); | ||
| v11.setSha256Checksum(v10.getSha256Checksum()); | ||
| v11.setCreateTime(v10.getCreateTime()); | ||
| v11.setUpdateTime(System.currentTimeMillis()); | ||
|
|
||
| // v1.1 新增字段:默认填充存储类型为 sblk(可根据路径推断) | ||
| v11.setSchemaVersion("v1.1"); | ||
| v11.setStorageType(inferStorageType(v10.getMetadataPath())); | ||
| return v11; |
There was a problem hiding this comment.
metadataPath 字段在升级过程中丢失。
upgradeV10ToV11 方法从 v10.getMetadataPath() 读取值来推断 storageType,但没有将 metadataPath 复制到 v11 对象中。这会导致升级后的 DTO 丢失该字段数据。
🐛 建议修复
private static VmMetadataDTO upgradeV10ToV11(VmMetadataDTO v10) {
VmMetadataDTO v11 = new VmMetadataDTO();
// 复制 v1.0 所有字段
v11.setVmUuid(v10.getVmUuid());
v11.setVmName(v10.getVmName());
v11.setNumCpus(v10.getNumCpus());
v11.setMemSize(v10.getMemSize());
v11.setVolumeList(v10.getVolumeList());
v11.setNicList(v10.getNicList());
v11.setSnapshotList(v10.getSnapshotList());
v11.setSystemTags(v10.getSystemTags());
v11.setResourceConfigs(v10.getResourceConfigs());
v11.setSha256Checksum(v10.getSha256Checksum());
v11.setCreateTime(v10.getCreateTime());
v11.setUpdateTime(System.currentTimeMillis());
+ v11.setMetadataPath(v10.getMetadataPath());
// v1.1 新增字段:默认填充存储类型为 sblk(可根据路径推断)
v11.setSchemaVersion("v1.1");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/MetadataVersionConverter.java`
around lines 49 - 68, The upgradeV10ToV11 method copies most fields but fails to
preserve metadataPath: it calls inferStorageType(v10.getMetadataPath()) but
never sets v11.setMetadataPath(...). Update upgradeV10ToV11 in
MetadataVersionConverter to copy the metadataPath from the old DTO (call
v11.setMetadataPath(v10.getMetadataPath())) before returning, ensuring
inferStorageType still uses v10.getMetadataPath() and VmMetadataDTO retains the
metadataPath after upgrade.
| @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) | ||
| public java.lang.String zoneUuid; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
echo "=== Checking Header API message file ==="
if [ -f "header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java" ]; then
echo "File found. Extracting field definitions:"
rg -n '@Param|public.*String|public.*Boolean' header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java | head -40
else
echo "File not found at expected path"
fi
echo
echo "=== Checking SDK action file ==="
if [ -f "sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java" ]; then
echo "File found. Extracting field definitions:"
rg -n '@Param|public.*String|public.*Boolean' sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java | head -40
else
echo "File not found at expected path"
fi
echo
echo "=== Full content of APIRegisterVmInstanceMsg ==="
cat -n header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java 2>/dev/null | head -80
echo
echo "=== Full content of RegisterVmInstanceAction ==="
cat -n sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java 2>/dev/null | head -120Repository: MatheMatrix/zstack
Length of output: 8139
移除 SDK 中的 zoneUuid 必填约束,或在服务端消息中补齐该字段。
RegisterVmInstanceAction 把 zoneUuid 设为必填(第 31-32 行),但 APIRegisterVmInstanceMsg 中完全不存在该字段。这导致 SDK 参数与服务端消息契约不一致,SDK 客户端会被迫提供一个服务端无法处理的参数。请统一两侧定义:
- 若服务端无需此字段,移除 SDK 中的
zoneUuid声明; - 若产品需要
zoneUuid,在APIRegisterVmInstanceMsg中添加该字段(含相应的参数校验)。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java` around lines
31 - 33, RegisterVmInstanceAction declares a required field zoneUuid that does
not exist on the server message APIRegisterVmInstanceMsg, causing a contract
mismatch; either remove the zoneUuid declaration (or its required=true
constraint) from RegisterVmInstanceAction in the SDK so the client no longer
sends it, or add a corresponding field zoneUuid with appropriate `@Param`
validation to APIRegisterVmInstanceMsg (and its validation logic) so the server
accepts and validates it—locate the RegisterVmInstanceAction class and the
APIRegisterVmInstanceMsg class and apply one of these two fixes to keep both
sides consistent.
| private void handle(APIRegisterVmInstanceMsg msg) { | ||
| APIRegisterVmInstanceReply event = new APIRegisterVmInstanceReply(msg.getId()); |
There was a problem hiding this comment.
变量命名与实际类型不符。
Line 1834 使用 APIRegisterVmInstanceReply event 但变量名为 event,这会导致混淆。根据 API 设计规范,同步操作使用 Reply,异步操作使用 Event。如果这是异步 API,应使用 APIRegisterVmInstanceEvent;如果是同步的,变量应命名为 reply。
🔧 建议修复
private void handle(APIRegisterVmInstanceMsg msg) {
- APIRegisterVmInstanceReply event = new APIRegisterVmInstanceReply(msg.getId());
+ APIRegisterVmInstanceEvent event = new APIRegisterVmInstanceEvent(msg.getId());或者如果确实是 Reply:
private void handle(APIRegisterVmInstanceMsg msg) {
- APIRegisterVmInstanceReply event = new APIRegisterVmInstanceReply(msg.getId());
+ APIRegisterVmInstanceReply reply = new APIRegisterVmInstanceReply();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1833 - 1834, In the handle(APIRegisterVmInstanceMsg msg) method the
local variable is declared as APIRegisterVmInstanceReply event which mismatches
naming conventions; either rename the variable to reply (e.g.,
APIRegisterVmInstanceReply reply = new APIRegisterVmInstanceReply(msg.getId()))
if this is a synchronous reply, or if the API is asynchronous change the type to
APIRegisterVmInstanceEvent and construct the event accordingly (e.g.,
APIRegisterVmInstanceEvent event = new APIRegisterVmInstanceEvent(msg.getId()))
and update subsequent references; ensure consistency between the type
(APIRegisterVmInstanceReply vs APIRegisterVmInstanceEvent) and the variable name
in the handle method.
| List<String> volumesString = vmMetadata.volumeVOs; | ||
|
|
||
| List<VolumeVO> volumes = new ArrayList<>(); | ||
| volumesString.forEach(v -> volumes.add(JSONObjectUtil.toObject(v, VolumeVO.class))); | ||
|
|
||
| List<VolumeVO> newVolumes = new ArrayList<>(); | ||
| volumes.forEach(v -> { | ||
| VolumeVO vo = new VolumeVO(); | ||
| // vo.setRootImageUuid(vo.getRootImageUuid()); | ||
| vo.setAccountUuid(msg.getSession().getAccountUuid()); | ||
| vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); | ||
| vo.setInstallPath(v.getInstallPath()); | ||
|
|
||
| vo.setCreateDate(v.getCreateDate()); | ||
| vo.setDescription(v.getDescription()); | ||
| vo.setName(v.getName()); | ||
| vo.setSize(v.getSize()); | ||
| vo.setActualSize(v.getActualSize()); | ||
| vo.setState(v.getState()); | ||
| vo.setUuid(v.getUuid()); | ||
| vo.setVmInstanceUuid(v.getVmInstanceUuid()); | ||
| vo.setType(v.getType()); | ||
| vo.setCreateDate(v.getCreateDate()); | ||
| vo.setLastOpDate(v.getLastOpDate()); | ||
| vo.setDeviceId(v.getDeviceId()); | ||
| vo.setStatus(v.getStatus()); | ||
| vo.setFormat(v.getFormat()); | ||
| vo.setShareable(v.isShareable()); | ||
| vo.setVolumeQos(v.getVolumeQos()); | ||
| vo.setLastDetachDate(v.getLastDetachDate()); | ||
| vo.setLastVmInstanceUuid(v.getLastVmInstanceUuid()); | ||
| vo.setLastAttachDate(v.getLastAttachDate()); | ||
| vo.setProtocol(v.getProtocol()); | ||
| newVolumes.add(vo); | ||
| }); | ||
| dbf.persistCollection(newVolumes); | ||
| trigger.next(); |
There was a problem hiding this comment.
缺少空值检查,可能导致 NullPointerException。
vmMetadata.volumeVOs 在访问前未进行空值检查。如果元数据中不包含 volume 信息,将抛出 NPE。同样的问题也存在于 vmMetadata.volumeSnapshots、vmMetadata.volumeSnapshotGroupVO 和 vmMetadata.volumeSnapshotGroupRefVO。
🛡️ 建议修复
flow(new NoRollbackFlow() {
String __name__ = "register-volume";
`@Override`
public void run(FlowTrigger trigger, Map data) {
List<String> volumesString = vmMetadata.volumeVOs;
+ if (volumesString == null || volumesString.isEmpty()) {
+ trigger.next();
+ return;
+ }
List<VolumeVO> volumes = new ArrayList<>();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1905 - 1941, The code reads vmMetadata.volumeVOs (and similarly
vmMetadata.volumeSnapshots, vmMetadata.volumeSnapshotGroupVO,
vmMetadata.volumeSnapshotGroupRefVO) without null checks which can cause NPEs;
before converting/iterating do a null (and optionally empty) check — e.g. if
vmMetadata.volumeVOs != null then convert JSON to VolumeVOs, build newVolumes,
and call dbf.persistCollection(newVolumes) only when newVolumes is non-empty;
apply the same guard pattern around handling of volumeSnapshots,
volumeSnapshotGroupVO, and volumeSnapshotGroupRefVO to skip processing when
those fields are null.
| private List<SystemTag> getResourceSystemTagFromSystem(String resourceType) throws IllegalAccessException, InstantiationException { | ||
| List<SystemTag> systemTags = new ArrayList<>(); | ||
|
|
||
| Set<Class<?>> classes = BeanUtils.reflections.getTypesAnnotatedWith(TagDefinition.class); | ||
| for (Class clazz : classes) { | ||
| Field[] fields = clazz.getDeclaredFields(); | ||
| for (Field field : fields) { | ||
| if (!SystemTag.class.isAssignableFrom(field.getType())) { | ||
| continue; | ||
| } | ||
|
|
||
| SystemTag systemTag = (SystemTag) field.get(clazz.newInstance()); | ||
|
|
||
| if (resourceType.equals(systemTag.getResourceClass().getName())) { | ||
| systemTags.add(systemTag); | ||
| } | ||
| } | ||
| } | ||
| return systemTags; | ||
| } |
There was a problem hiding this comment.
类型比较逻辑错误,方法将无法正确匹配任何 SystemTag。
Line 2146 使用 systemTag.getResourceClass().getName() 进行比较,返回完整类名(如 org.zstack.header.vm.VmInstanceVO)。但调用处(Line 2069)传入的是 VmInstanceVO.class.getSimpleName()(即 VmInstanceVO)。两者永远不会相等,导致方法始终返回空列表。
此外,clazz.newInstance() 已被废弃,应使用 clazz.getDeclaredConstructor().newInstance()。
🐛 建议修复
private List<SystemTag> getResourceSystemTagFromSystem(String resourceType) throws IllegalAccessException, InstantiationException {
List<SystemTag> systemTags = new ArrayList<>();
Set<Class<?>> classes = BeanUtils.reflections.getTypesAnnotatedWith(TagDefinition.class);
for (Class clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!SystemTag.class.isAssignableFrom(field.getType())) {
continue;
}
- SystemTag systemTag = (SystemTag) field.get(clazz.newInstance());
+ SystemTag systemTag = (SystemTag) field.get(clazz.getDeclaredConstructor().newInstance());
- if (resourceType.equals(systemTag.getResourceClass().getName())) {
+ if (systemTag.getResourceClass() != null &&
+ resourceType.equals(systemTag.getResourceClass().getSimpleName())) {
systemTags.add(systemTag);
}
}
}
return systemTags;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 2133 - 2152, The method getResourceSystemTagFromSystem is comparing
resourceType to systemTag.getResourceClass().getName() which returns the
fully-qualified name while callers pass a simple class name (e.g. VmInstanceVO),
so update the comparison to use systemTag.getResourceClass().getSimpleName().
Also replace the deprecated clazz.newInstance() with
clazz.getDeclaredConstructor().newInstance() when instantiating the declaring
class; adjust thrown exceptions or wrap/refine as needed to compile.
| private List<GlobalConfig> getResourceConfigFromSystem(String resourceType) throws IllegalAccessException, InstantiationException { | ||
| List<GlobalConfig> globalConfigs = new ArrayList<>(); | ||
|
|
||
| Set<Class<?>> classes = BeanUtils.reflections.getTypesAnnotatedWith(GlobalConfigDefinition.class); | ||
| for (Class clazz : classes) { | ||
| Field[] fields = clazz.getDeclaredFields(); | ||
| for (Field field : fields) { | ||
| if (!GlobalConfig.class.isAssignableFrom(field.getType())) { | ||
| continue; | ||
| } | ||
| GlobalConfig globalConfig = (GlobalConfig) field.get(clazz.newInstance()); | ||
|
|
||
| BindResourceConfig bindResourceConfig = field.getAnnotation(BindResourceConfig.class); | ||
| if (bindResourceConfig == null) { | ||
| continue; | ||
| } | ||
|
|
||
| List<String> bindResourceConfigs = Arrays.stream(bindResourceConfig.value()).map(Class::getName).collect(Collectors.toList()); | ||
|
|
||
| if (bindResourceConfigs.contains(resourceType)) { | ||
| globalConfigs.add(globalConfig); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return globalConfigs; | ||
| } |
There was a problem hiding this comment.
类型比较逻辑错误,与 getResourceSystemTagFromSystem 存在相同问题。
Line 2171 使用 Class::getName 获取完整类名,但 Line 2173 与传入的 resourceType(getSimpleName())比较,导致永远无法匹配。
🐛 建议修复
- List<String> bindResourceConfigs = Arrays.stream(bindResourceConfig.value()).map(Class::getName).collect(Collectors.toList());
+ List<String> bindResourceConfigs = Arrays.stream(bindResourceConfig.value()).map(Class::getSimpleName).collect(Collectors.toList());
- GlobalConfig globalConfig = (GlobalConfig) field.get(clazz.newInstance());
+ GlobalConfig globalConfig = (GlobalConfig) field.get(clazz.getDeclaredConstructor().newInstance());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 2154 - 2180, The method getResourceConfigFromSystem incorrectly
maps BindResourceConfig.value() to fully-qualified names via Class::getName and
then compares to resourceType (which is a simple name), causing no matches;
mirror the logic used in getResourceSystemTagFromSystem by comparing the same
name form—change the mapping of bindResourceConfig.value() to use
Class::getSimpleName (or alternatively normalize resourceType to fully-qualified
name) so the contains(resourceType) check in getResourceConfigFromSystem
succeeds; keep the BindResourceConfig, getResourceConfigFromSystem, and
resourceType symbols as the points to modify.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (3)
compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java (3)
9413-9416:⚠️ Potential issue | 🟠 Major根卷查询缺少空值兜底,存在 NPE 风险。
Line 9415-9416 直接读取
tuple.get(...),当根卷不存在时会触发空指针并导致请求无正常错误回复。🛠️ 建议修复
Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid) .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple(); + if (tuple == null) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("cannot find root volume for vm[uuid:%s]", msg.getUuid())); + bus.reply(msg, reply); + return; + } String primaryStorageUuid = tuple.get(0, String.class); String rootVolumeUuid = tuple.get(1, String.class);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9413 - 9416, The code in VmInstanceBase that queries the root volume using Q.New(VolumeVO.class)...findTuple() assigns tuple.get(...) directly to primaryStorageUuid and rootVolumeUuid and can NPE if no root volume is found; update the logic to check whether the returned tuple is null (or empty) after findTuple() and handle that case by returning a proper error response or throwing a readable exception instead of dereferencing the tuple (references: VolumeVO_, VolumeVO_.vmInstanceUuid, VolumeType.Root, tuple, msg.getUuid, primaryStorageUuid, rootVolumeUuid); ensure the error path logs context (vm UUID) and stops further processing.
9497-9507:⚠️ Potential issue | 🔴 Critical这里有未定义符号,当前代码无法编译。
vmUuid、volumeVos、snapshotVos、groupVos、tagVos、configVos、snapshotDto在当前方法内均未定义;另外 Line 9502 使用VmMetadataSerializer但未见对应 import。#!/bin/bash set -euo pipefail FILE="compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java" echo "== changed block ==" nl -ba "$FILE" | sed -n '9490,9512p' echo echo "== symbol appearances in file ==" for sym in vmUuid volumeVos snapshotVos groupVos tagVos configVos snapshotDto; do echo "-- ${sym} --" rg -n --type=java "\\b${sym}\\b" "$FILE" || true done echo echo "== import / usage check for VmMetadataSerializer ==" rg -n --type=java "VmMetadataSerializer" "$FILE" || true rg -n --type=java "import\\s+org\\.zstack\\.compute\\.vm\\.metadata\\.VmMetadataSerializer;" "$FILE" || true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9497 - 9507, The block uses undefined symbols (vmUuid, volumeVos, snapshotVos, groupVos, tagVos, configVos, snapshotDto) and an unimported VmMetadataSerializer; fix by sourcing/declaring the missing variables from the surrounding method or passing them as parameters (e.g., use the VmInstanceVO instance’s getUuid for vmUuid, retrieve or compute volumeVos/snapshotVos/groupVos/tagVos/configVos the same way other callers in VmInstanceBase do, and obtain snapshotDto from the snapshot processing context), and add the missing import for org.zstack.compute.vm.metadata.VmMetadataSerializer; update calls accordingly (VmMetadataConverter.convert(...) and VmMetadataSerializer.serialize(...)) so all referenced symbols exist in this method scope.
9502-9508:⚠️ Potential issue | 🟠 Major序列化结果被丢弃,敏感字段保护链路被绕过。
Line 9502 生成了
json,但 Line 9508 返回的是vmMetadata原始序列化;同时 Line 9511-9523 直接序列化SystemTagVO/ResourceConfigVO,会绕过 DTO/Serializer 的敏感字段处理。Also applies to: 9511-9523
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines 9502 - 9508, The code calls VmMetadataSerializer.serialize(vmDto) but then returns JSONObjectUtil.toJsonString(vmMetadata), discarding the sanitized json and bypassing sensitive-field handling; update the return to use the serialized result from VmMetadataSerializer.serialize(vmDto) (the local variable json) and ensure any serialization of SystemTagVO/ResourceConfigVO uses the same DTO/serializer pipeline instead of direct JSONObjectUtil.toJsonString on raw VO objects; adjust the logic around VolumeSnapshotReferenceVO/dbf.findByUuid and VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo) to operate on the DTOs prior to calling VmMetadataSerializer.serialize so sensitive fields are preserved by the serializer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java`:
- Around line 100-104: The comment for MAX_EXPONENT in
UpdateVmInstanceMetadataGC is inconsistent with the actual value (MAX_EXPONENT =
5); update the comment to reflect 2^5 = 32 and the resulting maximum delay of 10
* 32 = 320s (≈ 5.3 minutes), or if the intent was the larger delay, change
MAX_EXPONENT to 10 and adjust any dependent logic; locate the constant
MAX_EXPONENT in class UpdateVmInstanceMetadataGC and either correct the
explanatory text to match 5 or change the constant to the intended exponent and
update the comment accordingly.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java`:
- Around line 103-106: The comment points out that VmMetadataUpdateInterceptor
currently returns on any API failure without honoring the
`@MetadataImpact`(updateOnFailure) intent; update the logic in
VmMetadataUpdateInterceptor so that when apiEvent.getError() != null you look up
the pendingApis entry for apiEvent.getApiId() (or otherwise obtain the
MetadataImpact) and only return if impact.updateOnFailure() is false; otherwise
continue processing the failed API event. Ensure you reference the pendingApis
lookup and the MetadataImpact (or annotation wrapper) and call updateOnFailure()
to decide whether to skip or proceed.
- Around line 84-85: Remove the unused dead field by deleting the level field
from the MetadataImpactInfo class and its assignment in the constructor so the
class only stores vmUuid and whatever value is actively used; then implement the
comment's intended behavior in VmMetadataUpdateInterceptor by honoring the
MetadataImpact annotation's updateOnFailure flag: when an API fails, check the
MetadataImpact.updateOnFailure() value on the msg class (same annotation you
already read when creating MetadataImpactInfo) and only skip processing if
updateOnFailure is false—adjust the failure-handling path that currently always
skips pendingApis to consult this flag (you can add the flag to
MetadataImpactInfo if needed) so failed APIs with updateOnFailure=true still
trigger metadata updates.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java`:
- Around line 9400-9403: The current run(SyncTaskChain chain) calls
doHandleUpdateVmInstanceMetadata(msg) then immediately invokes chain.next(),
which breaks the intended per-VM serialization because
doHandleUpdateVmInstanceMetadata performs an asynchronous bus.send(...); fix by
making run defer chain.next() until the async work completes — i.e., change
doHandleUpdateVmInstanceMetadata(msg) (or the block that calls bus.send) to
accept a completion callback/future and invoke chain.next() in the bus.send
reply handler (or when that future completes); ensure chain.next() is removed
from the synchronous path and only called from within the bus.send callback or
completion handler so subsequent tasks for the same VM run only after the async
update finishes.
In `@core/src/main/java/org/zstack/core/gc/GarbageCollector.java`:
- Around line 203-219: The triggerGC() path has two issues: a potential NPE when
dbf.findByUuid(...) returns null before calling vo.getStatus(), and an
inconsistent claim condition versus loadFromVO() which expects
managementNodeUuid IS NULL; update triggerGC() to null-check the returned
GarbageCollectorVO from dbf.findByUuid(...) before accessing vo.getStatus(), and
when loading the VO for manual trigger either add
.isNull(GarbageCollectorVO_.managementNodeUuid) to the query or explicitly
verify vo.getManagementNodeUuid() == null before calling loadFromVO(); ensure
that if loadFromVO() returns false you do not call runTrigger() on an
uninitialized GC (i.e., only call setup()/prepare()/runTrigger() when the VO was
successfully claimed and loadFromVO() returned true).
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java`:
- Around line 9413-9416: The code in VmInstanceBase that queries the root volume
using Q.New(VolumeVO.class)...findTuple() assigns tuple.get(...) directly to
primaryStorageUuid and rootVolumeUuid and can NPE if no root volume is found;
update the logic to check whether the returned tuple is null (or empty) after
findTuple() and handle that case by returning a proper error response or
throwing a readable exception instead of dereferencing the tuple (references:
VolumeVO_, VolumeVO_.vmInstanceUuid, VolumeType.Root, tuple, msg.getUuid,
primaryStorageUuid, rootVolumeUuid); ensure the error path logs context (vm
UUID) and stops further processing.
- Around line 9497-9507: The block uses undefined symbols (vmUuid, volumeVos,
snapshotVos, groupVos, tagVos, configVos, snapshotDto) and an unimported
VmMetadataSerializer; fix by sourcing/declaring the missing variables from the
surrounding method or passing them as parameters (e.g., use the VmInstanceVO
instance’s getUuid for vmUuid, retrieve or compute
volumeVos/snapshotVos/groupVos/tagVos/configVos the same way other callers in
VmInstanceBase do, and obtain snapshotDto from the snapshot processing context),
and add the missing import for
org.zstack.compute.vm.metadata.VmMetadataSerializer; update calls accordingly
(VmMetadataConverter.convert(...) and VmMetadataSerializer.serialize(...)) so
all referenced symbols exist in this method scope.
- Around line 9502-9508: The code calls VmMetadataSerializer.serialize(vmDto)
but then returns JSONObjectUtil.toJsonString(vmMetadata), discarding the
sanitized json and bypassing sensitive-field handling; update the return to use
the serialized result from VmMetadataSerializer.serialize(vmDto) (the local
variable json) and ensure any serialization of SystemTagVO/ResourceConfigVO uses
the same DTO/serializer pipeline instead of direct JSONObjectUtil.toJsonString
on raw VO objects; adjust the logic around
VolumeSnapshotReferenceVO/dbf.findByUuid and
VmMetadataConverter.supplementSnapshotReference(snapshotDto, refVo) to operate
on the DTOs prior to calling VmMetadataSerializer.serialize so sensitive fields
are preserved by the serializer.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.javacompute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.javacore/src/main/java/org/zstack/core/gc/EventBasedGarbageCollector.javacore/src/main/java/org/zstack/core/gc/GarbageCollector.javacore/src/main/java/org/zstack/core/gc/TimeBasedGarbageCollector.java
| /** | ||
| * 指数退避的指数上限,防止左移溢出。 | ||
| * <p>2^10 = 1024,最大延迟 = 10 * 1024 = 10240s ≈ 2.8 小时。</p> | ||
| */ | ||
| private static final int MAX_EXPONENT = 5; |
There was a problem hiding this comment.
常量注释与实际值不符。
注释中说明 2^10 = 1024,最大延迟 = 10 * 1024 = 10240s ≈ 2.8 小时,但实际 MAX_EXPONENT = 5。
正确计算:2^5 = 32,最大延迟 = 10 * 32 = 320s ≈ 5.3 分钟。
📝 建议修正注释
/**
* 指数退避的指数上限,防止左移溢出。
- * <p>2^10 = 1024,最大延迟 = 10 * 1024 = 10240s ≈ 2.8 小时。</p>
+ * <p>2^5 = 32,最大延迟 = 10 * 32 = 320s ≈ 5.3 分钟。</p>
*/
private static final int MAX_EXPONENT = 5;📝 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.
| /** | |
| * 指数退避的指数上限,防止左移溢出。 | |
| * <p>2^10 = 1024,最大延迟 = 10 * 1024 = 10240s ≈ 2.8 小时。</p> | |
| */ | |
| private static final int MAX_EXPONENT = 5; | |
| /** | |
| * 指数退避的指数上限,防止左移溢出。 | |
| * <p>2^5 = 32,最大延迟 = 10 * 32 = 320s ≈ 5.3 分钟。</p> | |
| */ | |
| private static final int MAX_EXPONENT = 5; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/UpdateVmInstanceMetadataGC.java`
around lines 100 - 104, The comment for MAX_EXPONENT in
UpdateVmInstanceMetadataGC is inconsistent with the actual value (MAX_EXPONENT =
5); update the comment to reflect 2^5 = 32 and the resulting maximum delay of 10
* 32 = 320s (≈ 5.3 minutes), or if the intent was the larger delay, change
MAX_EXPONENT to 10 and adjust any dependent logic; locate the constant
MAX_EXPONENT in class UpdateVmInstanceMetadataGC and either correct the
explanatory text to match 5 or change the constant to the intended exponent and
update the comment accordingly.
| MetadataImpact impact = msg.getClass().getAnnotation(MetadataImpact.class); | ||
| pendingApis.put(apiMsg.getId(), new MetadataImpactInfo(vmUuid, impact.value())); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find MetadataImpact annotation definition
fd "MetadataImpact.java" -type fRepository: MatheMatrix/zstack
Length of output: 232
🏁 Script executed:
# Find MetadataImpactInfo class definition
fd "MetadataImpactInfo.java" -type fRepository: MatheMatrix/zstack
Length of output: 232
🏁 Script executed:
# Search for MetadataImpact annotation usage and definition
rg "class MetadataImpact|@interface MetadataImpact" -t javaRepository: MatheMatrix/zstack
Length of output: 301
🏁 Script executed:
# Get the full path and check the VmMetadataUpdateInterceptor file
fd "VmMetadataUpdateInterceptor.java" -type fRepository: MatheMatrix/zstack
Length of output: 232
🏁 Script executed:
find . -name "MetadataImpact.java" -o -name "MetadataImpactInfo.java"Repository: MatheMatrix/zstack
Length of output: 136
🏁 Script executed:
# Read the MetadataImpact annotation definition
rg "public `@interface` MetadataImpact" -A 10Repository: MatheMatrix/zstack
Length of output: 1074
🏁 Script executed:
# Read the VmMetadataUpdateInterceptor file, focusing on lines 84-85 and 189-196
cat -n compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java | sed -n '80,210p'Repository: MatheMatrix/zstack
Length of output: 5285
🏁 Script executed:
# Verify that level field is never used in MetadataImpactInfo
rg "info\.level|\.level" compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.javaRepository: MatheMatrix/zstack
Length of output: 93
🏁 Script executed:
# Check if MetadataImpact.updateOnFailure() is used anywhere in the file
rg "updateOnFailure" compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.javaRepository: MatheMatrix/zstack
Length of output: 132
level 字段存储后未使用,属于死代码
MetadataImpactInfo 的 level 字段在构造函数中赋值(第 195 行),但在整个类中从未被引用。建议删除此字段。
此外,第 103-105 行的注释提到应支持 @MetadataImpact(updateOnFailure=true) 来在 API 失败时仍进行更新,但当前代码并未实现此逻辑检查,总是在出错时直接跳过,与注释说明不符。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java`
around lines 84 - 85, Remove the unused dead field by deleting the level field
from the MetadataImpactInfo class and its assignment in the constructor so the
class only stores vmUuid and whatever value is actively used; then implement the
comment's intended behavior in VmMetadataUpdateInterceptor by honoring the
MetadataImpact annotation's updateOnFailure flag: when an API fails, check the
MetadataImpact.updateOnFailure() value on the msg class (same annotation you
already read when creating MetadataImpactInfo) and only skip processing if
updateOnFailure is false—adjust the failure-handling path that currently always
skips pendingApis to consult this flag (you can add the flag to
MetadataImpactInfo if needed) so failed APIs with updateOnFailure=true still
trigger metadata updates.
| // API 失败则跳过(除非 @MetadataImpact(updateOnFailure=true)) | ||
| if (apiEvent.getError() != null) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
updateOnFailure 属性注释与实现不一致
注释提到 @MetadataImpact(updateOnFailure=true) 应允许 API 失败时仍触发更新,但代码直接 return 跳过了所有失败的 API,未读取或检查该属性。
若该功能暂不实现,建议移除注释中的 updateOnFailure 描述以避免混淆;否则需补充逻辑:
MetadataImpact impact = pendingApis.get(apiEvent.getApiId())...;
if (apiEvent.getError() != null && !impact.updateOnFailure()) {
return;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java`
around lines 103 - 106, The comment points out that VmMetadataUpdateInterceptor
currently returns on any API failure without honoring the
`@MetadataImpact`(updateOnFailure) intent; update the logic in
VmMetadataUpdateInterceptor so that when apiEvent.getError() != null you look up
the pendingApis entry for apiEvent.getApiId() (or otherwise obtain the
MetadataImpact) and only return if impact.updateOnFailure() is false; otherwise
continue processing the failed API event. Ensure you reference the pendingApis
lookup and the MetadataImpact (or annotation wrapper) and call updateOnFailure()
to decide whether to skip or proceed.
| public void run(SyncTaskChain chain) { | ||
| doHandleUpdateVmInstanceMetadata(msg); | ||
| chain.next(); | ||
| } |
There was a problem hiding this comment.
链路串行化被提前 chain.next() 破坏。
Line 9402 在异步 bus.send(...) 发出后立即 chain.next(),会让同一 VM 的后续更新并发进入,和注释声明的串行保证不一致。
🛠️ 建议修复
- private void handle(UpdateVmInstanceMetadataMsg msg) {
+ private void handle(UpdateVmInstanceMetadataMsg msg) {
thdf.chainSubmit(new ChainTask(msg) {
@@
`@Override`
public void run(SyncTaskChain chain) {
- doHandleUpdateVmInstanceMetadata(msg);
- chain.next();
+ doHandleUpdateVmInstanceMetadata(msg, new NoErrorCompletion(chain) {
+ `@Override`
+ public void done() {
+ chain.next();
+ }
+ });
}
@@
- private void doHandleUpdateVmInstanceMetadata(UpdateVmInstanceMetadataMsg msg) {
+ private void doHandleUpdateVmInstanceMetadata(UpdateVmInstanceMetadataMsg msg, NoErrorCompletion completion) {
@@
bus.send(umsg, new CloudBusCallBack(msg) {
`@Override`
public void run(MessageReply r) {
UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
@@
bus.reply(msg, reply);
+ completion.done();
}
});
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java` around lines
9400 - 9403, The current run(SyncTaskChain chain) calls
doHandleUpdateVmInstanceMetadata(msg) then immediately invokes chain.next(),
which breaks the intended per-VM serialization because
doHandleUpdateVmInstanceMetadata performs an asynchronous bus.send(...); fix by
making run defer chain.next() until the async work completes — i.e., change
doHandleUpdateVmInstanceMetadata(msg) (or the block that calls bus.send) to
accept a completion callback/future and invoke chain.next() in the bus.send
reply handler (or when that future completes); ensure chain.next() is removed
from the synchronous path and only called from within the bus.send callback or
completion handler so subsequent tasks for the same VM run only after the async
update finishes.
| // 乐观锁:只认领 managementNodeUuid 为 NULL 的 GC(防止并发加载同一孤儿) | ||
| int updated = SQL.New(GarbageCollectorVO.class) | ||
| .eq(GarbageCollectorVO_.uuid, vo.getUuid()) | ||
| .isNull(GarbageCollectorVO_.managementNodeUuid) | ||
| .set(GarbageCollectorVO_.status, GCStatus.Idle) | ||
| .set(GarbageCollectorVO_.managementNodeUuid, Platform.getManagementServerId()) | ||
| .update(); | ||
|
|
||
| if (updated == 0) { | ||
| logger.debug(String.format("[GC] job[name:%s, id:%s] already claimed by another node, skip", | ||
| vo.getName(), vo.getUuid())); | ||
| return false; | ||
| } | ||
|
|
||
| gcMgr.registerGC(this); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 1) loadFromVO 调用点 =="
rg -nP --type=java -C4 '\bloadFromVO\s*\(\s*[^)]*\)' 2>/dev/null || echo "No matches found"
echo
echo "== 2) GC 认领相关字段写入点(managementNodeUuid/status) =="
rg -nP --type=java -C3 'GarbageCollectorVO_\.managementNodeUuid|setManagementNodeUuid\s*\(|GarbageCollectorVO_\.status' 2>/dev/null || echo "No matches found"
echo
echo "== 3) 管理器中 orphan / 手动触发入口(重点看查询条件是否要求 managementNodeUuid IS NULL) =="
fd 'GarbageCollectorManagerImpl.java' --exec head -320 {} \; 2>/dev/null || echo "File not found"Repository: MatheMatrix/zstack
Length of output: 45009
🏁 Script executed:
cat -n core/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.java | tail -100Repository: MatheMatrix/zstack
Length of output: 3830
🏁 Script executed:
# 查找 APITriggerGCJobMsg 的测试用例
fd -e java -e groovy | xargs grep -l "APITriggerGCJobMsg" 2>/dev/null | head -5
# 查看 setup/prepare 方法是否为 runTrigger 必须
rg -A5 "public void setup|public void prepare" core/src/main/java/org/zstack/core/gc/ --type=java
# 查看 runTrigger 实现
rg -B2 -A8 "public void runTrigger" core/src/main/java/org/zstack/core/gc/ --type=javaRepository: MatheMatrix/zstack
Length of output: 267
🏁 Script executed:
# 查看 runTrigger 的具体实现和所需的初始化
rg -A10 "void runTrigger\(\)" core/src/main/java/org/zstack/core/gc/ --type=java
# 查看 setup/prepare 具体做什么
rg -B1 -A10 "abstract void setup|abstract void prepare" core/src/main/java/org/zstack/core/gc/ --type=javaRepository: MatheMatrix/zstack
Length of output: 2294
🏁 Script executed:
# 查看完整的 runTrigger 实现
sed -n '119,160p' core/src/main/java/org/zstack/core/gc/GarbageCollector.java
# 查看 EventBasedGarbageCollector load 和 setup 的调用关系
sed -n '1,50p' core/src/main/java/org/zstack/core/gc/EventBasedGarbageCollector.java
# 查看 TimeBasedGarbageCollector load 和 prepare 的调用关系
sed -n '1,60p' core/src/main/java/org/zstack/core/gc/TimeBasedGarbageCollector.javaRepository: MatheMatrix/zstack
Length of output: 4486
🏁 Script executed:
# 查找所有调用 triggerGC/TriggerGcJobMsg 的地方
rg -B5 "triggerGC|TriggerGcJobMsg" core/src/main/java/org/zstack/core/gc/ --type=java | head -50
# 检查 API 层是否有过滤
fd APITriggerGCJobMsg.java --exec cat {} \;Repository: MatheMatrix/zstack
Length of output: 4593
🏁 Script executed:
# 确认 dbf.findByUuid 的 null 检查问题 (来自之前的learnings)
rg -B3 -A3 "dbf.findByUuid.*GarbageCollectorVO" core/src/main/java/org/zstack/core/gc/GarbageCollectorManagerImpl.javaRepository: MatheMatrix/zstack
Length of output: 661
修复 triggerGC() 中的空指针风险和认领条件不一致。
手动触发路径存在两个问题:
-
空指针风险(第 321 行):
dbf.findByUuid()在找不到记录时可能返回 null,但第 322 行直接调用vo.getStatus()无 null 检查。 -
认领条件不一致:孤儿加载明确要求
managementNodeUuid IS NULL(行 206-207),但手动触发的triggerGC()无此过滤。若传入已被其他节点认领的 GC 记录,loadFromVO()会因乐观锁失败而返回 false,导致 GC 未被正确初始化(setup()/prepare()未执行),但仍会继续调用runTrigger(),在未初始化对象上执行可能导致问题。
建议:
- 第 321 行添加 null 检查
triggerGC()获取 VO 时加上.isNull(GarbageCollectorVO_.managementNodeUuid)条件,或在调用前验证该字段为 NULL
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/src/main/java/org/zstack/core/gc/GarbageCollector.java` around lines 203
- 219, The triggerGC() path has two issues: a potential NPE when
dbf.findByUuid(...) returns null before calling vo.getStatus(), and an
inconsistent claim condition versus loadFromVO() which expects
managementNodeUuid IS NULL; update triggerGC() to null-check the returned
GarbageCollectorVO from dbf.findByUuid(...) before accessing vo.getStatus(), and
when loading the VO for manual trigger either add
.isNull(GarbageCollectorVO_.managementNodeUuid) to the query or explicitly
verify vo.getManagementNodeUuid() == null before calling loadFromVO(); ensure
that if loadFromVO() returns false you do not call runTrigger() on an
uninitialized GC (i.e., only call setup()/prepare()/runTrigger() when the VO was
successfully claimed and loadFromVO() returned true).
56b1219 to
6a5b8ca
Compare
Resolves: ZSV-10000 Change-Id: I746a6d7266686d637a6d76756861636a74766b74
6a5b8ca to
3004abb
Compare
Resolves: ZSV-1
Change-Id: I746a6d7266686d637a6d76756861636a74766b74
sync from gitlab !9082