Skip to content

<feature>[vm]: add MetadataImpact#3663

Open
zstack-robot-2 wants to merge 1 commit intofeature-zsv-5.0.0-vm-support-vtpm-and-secucebootfrom
sync/tao.gan/fb-reg-1-ZSV-11559@@3
Open

<feature>[vm]: add MetadataImpact#3663
zstack-robot-2 wants to merge 1 commit intofeature-zsv-5.0.0-vm-support-vtpm-and-secucebootfrom
sync/tao.gan/fb-reg-1-ZSV-11559@@3

Conversation

@zstack-robot-2
Copy link
Copy Markdown
Collaborator

APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373

sync from gitlab !9523

APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

概览

该变更引入了完整的虚拟机实例元数据管理系统,包括在主存储后端上更新、检索、扫描和清理元数据的功能,涵盖数据库模式、API端点、垃圾回收机制和存储插件集成。

变更清单

内聚组 / 文件 摘要
核心元数据配置与全局设置
compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java, compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
新增14项元数据相关的全局配置参数(启用开关、刷新策略、维护周期等)及系统标签常量,用于控制元数据刷新、清理和维护行为。
数据库模式与实体
conf/db/zsv/V5.0.0__schema.sql, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO*, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO*
新增两个数据库表(VmMetadataDirtyVO、VmMetadataFlushStateVO)及对应JPA实体和元模型类,用于持久化元数据脏状态和刷新状态。
API消息与事件(元数据操作)
header/src/main/java/org/zstack/header/storage/primary/API*VmInstanceMetadata*, header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadata*, header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorage*, header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadata*, header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadata*
新增扫描、清理、获取、注册和更新虚拟机元数据的REST API消息类、事件类和文档定义(包括中文文档),支持主存储上的元数据生命周期管理。
元数据路径与资源扩展点
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
新增三个扩展点接口,定义元数据路径构建、路径替换(迁移)和资源持久化的契约,使存储后端可通过SPI扩展支持不同的路径处理逻辑。
核心元数据数据结构与工具
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java, header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java, header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java, header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
新增元数据DTO、常量定义、资源元数据包装类和分类枚举,为元数据序列化、传输和分类提供统一的数据结构支持。
元数据消息与内部流程
header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadata*, header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg, header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg, header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg, header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg, header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg
新增内部消息和回复类,支持主存储后端与管理层之间的元数据操作通信(包括更新、获取、扫描、清理和卷前缀变基)。
扫描结果数据模型
header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java, header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy
新增元数据扫描条目类和API文档,用于返回存储上发现的虚拟机元数据的详细信息(包括UUID、名称、大小、更新时间等)。
元数据影响注解与解析器
header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java, header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
新增@MetadataImpact注解(支持CONFIG和STORAGE两种影响类型)及其解析器接口,用于在API消息级别声明和追踪元数据变更的影响范围。
VM实例状态与服务接口
header/src/main/java/org/zstack/header/vm/VmInstanceState.java, header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
扩展VM实例状态机新增Registering状态,并新增元数据脏标记服务接口以支持可选的元数据变更跟踪。
计算层工作流与垃圾回收
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java, compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java, compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
新增VM元数据清理工作流(处理VM删除时的元数据清理)、垃圾回收任务(定期重试失败的清理操作),以及API拦截器(验证API RegisterVmInstanceFromMetadata的参数)。
本地存储后端实现
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java, plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java, plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java, plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator*
本地存储后端实现元数据路径构建、验证和HTTP代理逻辑;LocalStorageVmMetadataExtension提供路径计算和持久化逻辑;LocalStorageSimulator注册模拟器端点用于测试。
NFS存储后端实现
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java, plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java, plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java, plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
NFS存储后端类似实现元数据操作的消息路由、KVM命令转发、路径处理扩展点和代理命令定义。
主存储基础设施扩展
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
扩展主存储基类以支持新的元数据相关消息路由、API端点处理(扫描API)和默认错误回复。
元数据影响注解应用(API消息)
header/src/main/java/org/zstack/header/vm/API*Msg.java, header/src/main/java/org/zstack/header/storage/snapshot/API*Msg.java, header/src/main/java/org/zstack/header/tag/API*Msg.java, header/src/main/java/org/zstack/header/volume/API*Msg.java
对50+个现有API消息类新增@MetadataImpact注解,声明这些操作对虚拟机元数据配置或存储的影响程度。
SDK生成与API助手
sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadata*, sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorage*, sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadata*, sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorage*, sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadata*, sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java, sdk/src/main/java/SourceClassMap.java, testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
生成SDK操作类、结果类和辅助工具,支持客户端调用新增的元数据管理API,并在测试框架中提供便捷的API调用包装。

序列图

sequenceDiagram
    participant Client
    participant API as API Handler<br/>(PrimaryStorageBase)
    participant PS as Primary Storage<br/>(LocalStorage/NFS)
    participant Host as KVM Host<br/>Backend
    participant DB as Database

    Client->>API: APIScanVmInstanceMetadataFromPrimaryStorageMsg
    activate API
    API->>PS: ScanVmInstanceMetadataFromPrimaryStorageMsg
    deactivate API
    activate PS
    PS->>Host: HTTP POST /metadata/scan
    deactivate PS
    activate Host
    Host->>DB: Query local VM metadata files
    Host-->>PS: ScanVmMetadataRsp + VmMetadataScanEntry[]
    deactivate Host
    activate PS
    PS-->>API: ScanVmInstanceMetadataFromPrimaryStorageReply
    deactivate PS
    activate API
    API->>API: Filter entries<br/>(remove nulls, templates)
    API-->>Client: APIScanVmInstanceMetadataFromPrimaryStorageEvent
    deactivate API
Loading
sequenceDiagram
    participant VM as VM Expunge
    participant Flow as VmExpungeMetadata<br/>Flow
    participant GC as Cleanup GC
    participant PS as Primary Storage
    participant Host as Host Backend

    VM->>Flow: run(vmInstanceSpec)
    activate Flow
    alt Metadata cleanup needed
        Flow->>PS: CleanupVmInstanceMetadataOnPrimaryStorageMsg
        activate PS
        PS->>Host: HTTP POST /metadata/cleanup
        deactivate PS
        Host-->>Flow: Success Reply
        Flow->>Flow: Log success
    else Cleanup failed
        Flow->>Flow: Create cleanup GC job
        Flow->>GC: Schedule retry with interval
        GC->>GC: Wait for interval
        GC->>PS: Retry cleanup
    end
    deactivate Flow
Loading

代码审查工作量估算

🎯 4 (复杂) | ⏱️ ~75 分钟

诗歌

🐰 元数据的魔法来临,
扫描、清理、注册样样通。
存储的奥秘被解锁,
主机与虚机心意相通。
垃圾回收守护每一个角落,
API之路绚烂如彩虹。✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/tao.gan/fb-reg-1-ZSV-11559@@3

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (16)
header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java (1)

10-13: 可将 result 初始化延后到空判之后。

当前在 fieldValues 为空时会产生一次不必要的 ArrayList 分配;这是小优化,可后续顺手调整。

♻️ 建议修改
     default List<String> batchResolveVmUuids(List<String> fieldValues) {
-        List<String> result = new ArrayList<>();
         if (fieldValues == null || fieldValues.isEmpty()) {
-            return result;
+            return new ArrayList<>();
         }
+        List<String> result = new ArrayList<>(fieldValues.size());
         for (String v : fieldValues) {
🤖 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/metadata/VmUuidFromApiResolver.java`
around lines 10 - 13, In VmUuidFromApiResolver, avoid allocating the ArrayList
when fieldValues is null or empty by moving the List<String> result = new
ArrayList<>(); declaration to after the null/empty check; specifically, in the
method that currently creates result before "if (fieldValues == null ||
fieldValues.isEmpty()) { return result; }", change the flow so you first return
an empty list (Collections.emptyList() or new ArrayList<>()) on the null/empty
check and only allocate the mutable result list when you know fieldValues
contains elements that must be processed.
sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java (1)

3-3: 冗余的同包导入。

VmInstanceInventoryRegisterVmInstanceFromMetadataResult 位于同一包 org.zstack.sdk 中,无需显式导入。

🔧 建议移除冗余导入
 package org.zstack.sdk;
 
-import org.zstack.sdk.VmInstanceInventory;
-
 public class RegisterVmInstanceFromMetadataResult {
🤖 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/RegisterVmInstanceFromMetadataResult.java`
at line 3, The import of VmInstanceInventory is redundant because
VmInstanceInventory and the class RegisterVmInstanceFromMetadataResult are in
the same package (org.zstack.sdk); remove the line importing VmInstanceInventory
from RegisterVmInstanceFromMetadataResult so the class relies on the package
scope instead of an unnecessary explicit import.
header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java (1)

11-27: “Required”语义与默认值存在冲突。

Line 13、Line 19、Line 25 注释写“Required”,但 resolverfieldupdateOnFailure 都有默认值,调用方遗漏配置时会静默生效为默认值。建议二选一:

  1. 在注解消费端对 Impact != NONE 做强校验;
  2. 将注释改为“可选且有默认行为”,避免误导。
🤖 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/metadata/MetadataImpact.java`
around lines 11 - 27, The Javadoc says resolver, field, and updateOnFailure are
"Required" but they have defaults; update the annotation consumer to enforce the
requirement when Impact != NONE (or specifically when impact is CONFIG or
STORAGE): when processing MetadataImpact, validate that resolver() and field()
are non-empty (and explicitly handle updateOnFailure if your logic needs an
explicit value) and throw a clear configuration/validation exception if missing;
alternatively, if you prefer keeping silent defaults, update the Javadoc on
resolver(), field(), and updateOnFailure() to state they are optional and
describe the default behavior instead of saying "Required".
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java (1)

6-15: 建议为扩展点接口方法补充 Javadoc 说明。

该接口是跨模块扩展契约,当前方法缺少注释会降低实现方对参数语义(如 resourceTypenow、回滚语义)的理解一致性。

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/vm/metadata/VmMetadataResourcePersistExtensionPoint.java`
around lines 6 - 15, Add Javadoc to the VmMetadataResourcePersistExtensionPoint
interface and to each method (getPrimaryStorageType, afterVolumePersist,
afterSnapshotPersist, afterRegistrationRollback) describing purpose, parameter
semantics (explain primaryStorageUuid, resourceUuid, resourceType meaning,
hostUuid, size units, and what Timestamp now represents), and the rollback
semantics for afterRegistrationRollback (what callers should expect when it's
invoked and whether implementors must be idempotent). Also ensure no redundant
access modifiers are used on the interface methods (they should remain
package-private syntax-wise as in interface defaults) and keep the Javadoc
concise and consistent with project coding guidelines.
sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java (1)

5-12: 建议使用泛型参数化 List 类型。

java.util.List 缺少泛型参数,建议使用 java.util.List<VmMetadataScanEntry> 以提升类型安全性。如果此类是自动生成的,可忽略此建议。

🤖 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/ScanVmInstanceMetadataFromPrimaryStorageResult.java`
around lines 5 - 12, The vmInstanceMetadata field and its accessor methods in
ScanVmInstanceMetadataFromPrimaryStorageResult use a raw java.util.List; change
the field declaration and the signatures of setVmInstanceMetadata and
getVmInstanceMetadata to use a parameterized type (e.g.,
java.util.List<VmMetadataScanEntry>) to restore generic type safety, updating
the types in the field name vmInstanceMetadata and the methods
setVmInstanceMetadata and getVmInstanceMetadata accordingly; if
VmMetadataScanEntry is in a different package, import or fully qualify it.
header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy (1)

7-66: 字段描述为空,建议补充说明。

所有字段的 desc 均为空字符串,缺少对各字段含义的描述。作为 API 文档,建议为每个字段添加有意义的中文描述,以帮助 API 使用者理解各字段的用途。

例如:

  • vmUuid: "虚拟机UUID"
  • vmName: "虚拟机名称"
  • sizeBytes: "元数据文件大小(字节)"
  • incomplete: "元数据是否不完整"
🤖 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/VmMetadataScanEntryDoc_zh_cn.groovy`
around lines 7 - 66, 所有 field 的 desc 目前为空,按 API 文档要求请为每个字段补充中文描述以说明含义;在
VmMetadataScanEntryDoc_zh_cn.groovy 中更新各 field 条目(比如 vmUuid, vmName, vmCategory,
architecture, schemaVersion, metadataPath, hostUuid, sizeBytes, lastUpdateTime,
incomplete)将 desc 从 "" 替换为简短中文说明(例如 vmUuid: "虚拟机 UUID", vmName: "虚拟机名称",
sizeBytes: "元数据文件大小(字节)", incomplete: "元数据是否不完整" 等),保持风格一致并在必要时补充单位或取值说明以便文档清晰。
header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy (1)

5-23: 文档字段不完整,缺少实际响应字段。

根据 APIUpdateVmInstanceMetadataEvent.java 的定义,该事件应包含 totalCleanedtotalFailedfailedVmUuids 字段,但此文档仅记录了通用的 successerror 字段。

建议补充缺失的业务字段文档,以保持 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/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy`
around lines 5 - 23, The doc is missing the business response fields declared in
APIUpdateVmInstanceMetadataEvent; add field entries for totalCleaned (integer,
总共清理的条目数), totalFailed (integer, 清理失败的条目数) and failedVmUuids (list/string array,
清理失败的云主机UUID列表) in APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy alongside
the existing success and error blocks so the generated API docs reflect the
actual event properties; use the same "since" version as the file (5.0.0) and
include appropriate types and descriptions matching the Java fields in
APIUpdateVmInstanceMetadataEvent.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java (1)

3-11: 建议为接口方法添加 Javadoc 注释。

根据编码规范,接口方法应配有有效的 Javadoc 注释,以说明每个方法的用途、参数含义和返回值。例如:

  • validateMetadataPath 的返回值语义(成功返回什么,失败返回什么)
  • buildVmMetadataPathbuildMetadataDir 的路径格式约定

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/vm/metadata/VmMetadataPathBuildExtensionPoint.java`
around lines 3 - 11, 为 VmMetadataPathBuildExtensionPoint 接口的每个方法添加 Javadoc 注释:对
getPrimaryStorageType、buildVmMetadataPath、buildMetadataDir、validateMetadataPath
和 requireHostForCleanup 逐个说明用途、参数含义(例如
primaryStorageUuid、vmInstanceUuid)、返回值语义(明确 validateMetadataPath 在成功/失败时返回什么——例如
null/空串或错误描述)、以及路径格式约定和任何约束(如是否包含前缀/后缀、分隔符规则);保持注释简洁明确以便实现者和调用方理解各方法契约。
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (2)

56-56: 多余的空行

第56行有一个多余的空行。

🤖 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/VmExpungeMetadataFlow.java` at
line 56, VmExpungeMetadataFlow.java 中第56行存在多余空行,删除该空行以保持代码格式一致;定位到类
VmExpungeMetadataFlow(或相关方法体)并移除该多余的空白行即可,不影响逻辑或其他符号。

102-103: 冗余的final变量声明

finalPsUuidfinalHostUuid 可以简化。由于 psUuid 在第102行之后不再被修改,而 hostUuid 虽然在第84行被重新赋值,但在第101行之后也不再改变,这两个变量可以用更简洁的方式处理。

♻️ 建议的简化方式

可以将 hostUuid 的处理逻辑提前,使其成为effectively final,或直接在lambda中使用:

-        final String finalPsUuid = psUuid;
-        final String finalHostUuid = hostUuid;
-
         bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid);
+        final String msgPsUuid = psUuid;
+        final String msgHostUuid = hostUuid;
         bus.send(cmsg, new CloudBusCallBack(trigger) {
             `@Override`
             public void run(MessageReply reply) {
                 if (reply.isSuccess()) {
                     logger.info(String.format("[MetadataExpunge] successfully deleted metadata for vm[uuid:%s] on ps[uuid:%s]",
-                            vmUuid, finalPsUuid));
+                            vmUuid, msgPsUuid));
                 } else {
                     logger.warn(String.format("[MetadataExpunge] failed to delete metadata for vm[uuid:%s] on ps[uuid:%s]: %s, " +
-                            "submitting GC job for retry", vmUuid, finalPsUuid, reply.getError()));
-                    submitGC(finalPsUuid, vmUuid, rootVolumeUuid, metadataPath, finalHostUuid);
+                            "submitting GC job for retry", vmUuid, msgPsUuid, reply.getError()));
+                    submitGC(msgPsUuid, vmUuid, rootVolumeUuid, metadataPath, msgHostUuid);
                 }
                 trigger.next();
             }
         });
🤖 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/VmExpungeMetadataFlow.java`
around lines 102 - 103, 在 VmExpungeMetadataFlow 中消除冗余的 final 变量:不要创建 finalPsUuid
和 finalHostUuid 两个中间变量,改为直接在 lambda/匿名类中使用已是 effectively final 的 psUuid 或将
hostUuid 的赋值逻辑提前使其成为 effectively final;如果必须显式固定值,保留单个局部 final 变量(例如 finalPsUuid
或 finalHostUuid 之一)并移除另一个,确保引用的变量在创建 lambda
之后不再被修改(参考符号:psUuid、hostUuid、finalPsUuid、finalHostUuid)。
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java (2)

78-80: 修改 @GC 字段可能导致问题

在第80行,hostUuid 被设置为 null。由于 hostUuid 是用 @GC 注解标记的字段,这个修改可能会被持久化。如果GC作业在本次触发后失败并被重新调度,下次触发时 hostUuid 将为 null,即使原始提交时有有效值。

建议使用局部变量来避免修改持久化字段:

♻️ 建议的修复
+        String effectiveHostUuid = hostUuid;
         if (hostUuid != null && !dbf.isExist(hostUuid, HostVO.class)) {
             if (requireHost) {
                 logger.debug(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists " +
                                 "and ps[uuid:%s, type:%s] requires host for cleanup, cancel gc for vm[uuid:%s]",
                         hostUuid, primaryStorageUuid, psType, vmUuid));
                 completion.cancel();
                 return;
             }

             logger.info(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists for vm[uuid:%s], " +
                     "clear hostUuid and let the primary storage backend pick an available host", hostUuid, vmUuid));
-            hostUuid = null;
+            effectiveHostUuid = null;
         }

         CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg();
         msg.setPrimaryStorageUuid(primaryStorageUuid);
         msg.setVmInstanceUuid(vmUuid);
         msg.setRootVolumeUuid(rootVolumeUuid);
         msg.setMetadataPath(metadataPath);
-        msg.setHostUuid(hostUuid);
+        msg.setHostUuid(effectiveHostUuid);
🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`
around lines 78 - 80, The code in CleanupVmInstanceMetadataOnPrimaryStorageGC
sets the `@GC-annotated` field hostUuid to null which may persist that change;
instead, stop mutating the field and use a local variable for the temporary "no
longer exists" state and subsequent logic. Locate the hostUuid usage in the
class (field hostUuid and the block that logs and sets hostUuid = null),
introduce a local variable (e.g., localHostUuid) to hold the nullable value for
this GC run, leave the hostUuid field untouched, and update any subsequent
references in this method to use the local variable so no persistent `@GC` field
is modified.

50-50: 使用 PrimaryStorageAO_ 而非 PrimaryStorageVO_

第50行使用 PrimaryStorageAO_ 进行查询选择和条件判断。虽然这可能是有意为之(AO可能是VO的父类),但与第43行使用 PrimaryStorageVO.class 检查存在性不一致。建议统一使用 PrimaryStorageVO_

🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`
at line 50, The query at String psType = Q.New(PrimaryStorageVO.class)... is
using PrimaryStorageAO_ for the generated metamodel but the existence check
above uses PrimaryStorageVO.class, causing inconsistency; change the metamodel
references to PrimaryStorageVO_ so the select and eq use PrimaryStorageVO_.type
and PrimaryStorageVO_.uuid (i.e., update the Q.New(...).select(...) and .eq(...)
calls to reference PrimaryStorageVO_ instead of PrimaryStorageAO_) to keep the
type/VO usage consistent with the earlier check in
CleanupVmInstanceMetadataOnPrimaryStorageGC.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java (1)

96-104: 接口方法签名与现有模式不一致

新增的handle方法签名为 (msg, hostUuid, completion),而现有的handle方法(第25-55行)签名为 (PrimaryStorageInventory inv, msg, completion)。这种不一致可能会造成维护上的困惑。

如果这是有意为之的设计(例如metadata操作不需要inventory参数),建议在接口中添加注释说明原因。

🤖 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/NfsPrimaryStorageBackend.java`
around lines 96 - 104, The new handle method signatures in
NfsPrimaryStorageBackend
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg...),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg...),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...),
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)) are inconsistent with the
existing handle pattern that accepts (PrimaryStorageInventory inv, msg,
completion); update the interface so these metadata-related handles either
follow the same signature order and include PrimaryStorageInventory as the first
parameter, or if the omission of PrimaryStorageInventory is intentional, add a
clear Javadoc comment on each of the five methods explaining why we use (msg,
hostUuid, completion) and document the design rationale; ensure the change is
applied to the method declarations in NfsPrimaryStorageBackend and any
implementing classes to keep the API consistent.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)

1994-1998: 中间变量可以简化

第1995行声明 finalHostUuid,然后在第1996行同时检查 hostUuid 是否为null。这个模式可以简化。

♻️ 建议的简化
         String hostUuid = msg.getHostUuid();
-        String finalHostUuid = hostUuid;
-        if (hostUuid == null || !connectedHosts.stream().anyMatch(h -> h.getUuid().equals(finalHostUuid))) {
+        if (hostUuid == null || connectedHosts.stream().noneMatch(h -> h.getUuid().equals(hostUuid))) {
             hostUuid = connectedHosts.get(0).getUuid();
         }

同样的模式在第2048-2051行也存在。

🤖 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 1994 - 1998, Remove the unnecessary intermediate variable
finalHostUuid by introducing a single final/local capture for the lambda and
using it in the containment check; e.g. create a final String candidate =
hostUuid (or final String host = hostUuid) and use
connectedHosts.stream().anyMatch(h -> h.getUuid().equals(candidate)) in the if
condition, then assign hostUuid = connectedHosts.get(0).getUuid() when needed;
apply the same simplification to the identical pattern around the code that
currently lives at lines ~2048-2051 so both checks use a single captured local
instead of two variables.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (1)

38-54: buildMetadataDir()buildVmMetadataPath() 存在代码重复。

两个方法都执行相同的 URL 查询和空值检查。可以考虑提取一个私有方法来获取和验证 URL。

♻️ 可选的重构建议
+    private String getPrimaryStorageUrl(String primaryStorageUuid) {
+        String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
+        if (url == null) {
+            throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid));
+        }
+        return normalizeBaseDir(url);
+    }
+
     `@Override`
     public String buildMetadataDir(String primaryStorageUuid) {
-        String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
-        if (url == null) {
-            throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid));
-        }
-        return String.format("%s/%s", normalizeBaseDir(url), VmInstanceMetadataConstants.METADATA_DIR_NAME);
+        return String.format("%s/%s", getPrimaryStorageUrl(primaryStorageUuid), VmInstanceMetadataConstants.METADATA_DIR_NAME);
     }

     `@Override`
     public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) {
-        String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
-        if (url == null) {
-            throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid));
-        }
-        return String.format("%s/%s/%s%s", normalizeBaseDir(url), VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX);
+        return String.format("%s/%s/%s%s", getPrimaryStorageUrl(primaryStorageUuid), VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX);
     }
🤖 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/LocalStorageVmMetadataExtension.java`
around lines 38 - 54, Both buildMetadataDir and buildVmMetadataPath repeat the
same PrimaryStorageVO URL query and null check; extract a private helper (e.g.,
getPrimaryStorageUrl(String primaryStorageUuid)) that performs
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(...).findValue()
and throws the CloudRuntimeException if null, then have buildMetadataDir and
buildVmMetadataPath call this helper and reuse normalizeBaseDir and
VmInstanceMetadataConstants to build their return values.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)

918-962: 新增的 DTO 类结构正确,遵循了现有模式。

这些命令和响应类与文件中的现有模式一致。有一个小的风格建议:

PrefixRebaseBackingFilesCmdPrefixRebaseBackingFilesRsp 使用了完全限定的父类名(LocalStorageKvmBackend.AgentCommand / LocalStorageKvmBackend.AgentResponse),但由于它们已经在 LocalStorageKvmBackend 类内部定义,可以直接使用简短的类名 AgentCommand / AgentResponse,与其他嵌套类保持一致。

♻️ 可选的风格改进
-public static class PrefixRebaseBackingFilesCmd extends LocalStorageKvmBackend.AgentCommand {
+public static class PrefixRebaseBackingFilesCmd extends AgentCommand {
     public List<String> filePaths;
     public String oldPrefix;
     public String newPrefix;
 }

-public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.AgentResponse {
+public static class PrefixRebaseBackingFilesRsp extends AgentResponse {
     public int rebasedCount;
 }
🤖 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 918 - 962, The two nested classes PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp use fully qualified parent types
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse;
change their declarations to extend the inner types directly (AgentCommand and
AgentResponse) to match the style of the other DTOs in LocalStorageKvmBackend
(e.g., use "class PrefixRebaseBackingFilesCmd extends AgentCommand" and "class
PrefixRebaseBackingFilesRsp extends AgentResponse"), keeping all fields and
behavior unchanged.
🤖 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/VmInstanceApiInterceptor.java`:
- Around line 1360-1362: The current call throws ApiMessageInterceptionException
using argerr(error) which treats the dynamic error string as a format pattern;
change the throw site (the block that calls ext.validateMetadataPath(psUuid,
path) and throws new ApiMessageInterceptionException(...)) to pass the message
as an argument, e.g. use argerr("%s", error) so the returned error is safely
formatted as data rather than a format string; keep the rest of the logic
(checking error != null and throwing ApiMessageInterceptionException) unchanged.

In `@header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java`:
- Line 12: Implement a Spring bean named "NicUuidToVmUuidResolver" that
implements the VmUuidFromApiResolver interface and resolves a NIC UUID to its VM
UUID; create a class (e.g., NicUuidToVmUuidResolver) implementing
VmUuidFromApiResolver, inject the appropriate NIC/VM lookup service or DAO used
elsewhere in the project, implement the resolver method to fetch the VM UUID by
the given nicUuid (handling not-found and errors consistently with other
resolvers), and register it as a Spring bean with the id
"NicUuidToVmUuidResolver" so that the
`@MetadataImpact`(resolver="NicUuidToVmUuidResolver") usages in
APIChangeVmNicStateMsg, APIDetachL3NetworkFromVmMsg, APIUpdateVmNicDriverMsg and
APIChangeVmNicNetworkMsg resolve correctly.

In
`@header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy`:
- Line 20: 在 APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中的
desc """""" 为空,会在生成的 API 文档中留下空白说明;请用一条最小语义的中文描述替换该空字符串,例如说明此 API
获取虚拟机在主存储上的元数据(或类似简短句子),以便在类/文件
APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中的 desc
字段有实际内容并避免空说明。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 8-10: Add Javadoc for the interface methods
getPrimaryStorageType() and calculatePathReplacements(String targetPsUuid,
List<String> allOldPaths) describing their purpose, expected inputs and outputs,
null/empty handling, exception behavior and any concurrency or lifecycle
constraints; also document the nested PathReplacementResult class fields and
meaning (e.g., mapping semantics, order guarantees). Ensure the Javadoc follows
project conventions (brief summary + `@param/`@return/@throws where applicable)
and do not add extra modifiers to the interface methods.

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 7-24: 为接口 VmUuidFromApiResolver 的两个方法 resolveVmUuid 和
batchResolveVmUuids 添加完整的 Javadoc:在 resolveVmUuid 上说明方法用途(将 API 字段值解析为 VM
UUID)、参数说明 (`@param` fieldValue 允许为 null 或空时的行为)、返回值说明 (`@return` 返回解析出的 VM UUID 或
null);在 batchResolveVmUuids 上说明批量解析行为、对 null 或空集合以及单个 null 元素的处理规则、返回值描述(返回解析成功的
VM UUID 列表,保持输入顺序或说明不会包含 nulls);遵守接口规范不要为接口方法添加多余修饰符(如 public)。

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3371-3377: The cleanup and update branches use different sync keys
causing races; make both branches return the same VM-scoped sync signature so
cleanup and update serialize on the same key. Locate the getSyncSignature
implementations (the one returning "update-metadata-on-ps-%s" and the other
returning "update-metadata-on-ps-%s-%s") and change them to use a single helper
(e.g., buildVmSyncSignature(vmUuid)) that produces a signature based only on the
VM UUID (for example "update-metadata-on-ps-{vmUuid}"), and ensure both
getSyncLevel implementations remain consistent so cleanup and update acquire the
same lock.
- Around line 910-911: 当前分支对 RebaseVolumeBackingFileOnPrimaryStorageMsg 直接调用本类的
handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg),绕过了父类
PrimaryStorageBase.handleLocalMessage() 中的 checkPrimaryStatus(msg) 校验,导致
maintenance 状态检查失效;请将该分支改为交由 super.handleLocalMessage(msg) 处理(即不要直接调用本类 handle
方法),让父类先执行 checkPrimaryStatus(msg) 再按多态分发到本类的
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg)。

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 375-383: The prefixRebaseBackingFiles endpoint does not record
incoming commands for tests; add a List<PrefixRebaseBackingFilesCmd> field named
prefixRebaseBackingFilesCmds to LocalStorageSimulatorConfig and update the
LocalStorageSimulator.prefixRebaseBackingFiles method to append the parsed
PrefixRebaseBackingFilesCmd (cmd) to config.prefixRebaseBackingFilesCmds before
building/replying the PrefixRebaseBackingFilesRsp so tests can inspect the
received commands. Ensure imports and field visibility match other config lists
(e.g., writeVmMetadataCmds) and follow the same null-safety/initialization
pattern.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 151-168: The createResourceRef method in
LocalStorageVmMetadataExtension has a TOCTOU race: the exists check
(Q.New(...).isExists()) can race and dbf.persist(ref) may throw a
unique-constraint/duplicate-key error; replace the check-with-insert pattern
with a safe upsert or wrap dbf.persist(ref) in a try/catch to handle constraint
violations gracefully (detect the duplicate-key/constraint exception thrown by
the ORM/DB and treat it as a no-op), or use a DB-level upsert function if
available so concurrent callers won't fail; update createResourceRef to catch
the specific persistence exception and log/debug that the ref already exists
instead of letting the error propagate.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 136-141: NfsPrimaryStorageKVMBackend adds five new request paths
(WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH,
CLEANUP_VM_METADATA_PATH, NFS_PREFIX_REBASE_BACKING_FILES_PATH) but the KVM
agent has no registered handlers for them; add corresponding handler
registrations in the KVM agent (plugin/kvm) following the pattern used by
LocalStorageSimulator/LocalStorageKvmBackend: implement request processors for
each path that deserialize the incoming request, perform the same semantics
expected by NfsPrimaryStorageKVMBackend, and register them with the agent’s HTTP
dispatcher so the agent will not return 404s for these endpoints.

In
`@resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java`:
- Line 8: The project is missing an implementation of
ResourceUuidToVmUuidResolver referenced by the `@MetadataImpact` on
APIDeleteResourceConfigMsg (resourceUuid of type ResourceVO.class); implement a
Spring bean class named e.g. ResourceUuidToVmUuidResolverImpl that implements
ResourceUuidToVmUuidResolver, annotate it as a component/service, and implement
resolve(resourceUuid) to: 1) check if resourceUuid is a VM UUID (query
VmInstanceVO) and return it; 2) check known VM-related resources (VolumeVO ->
getVmInstanceUuid, VmNicVO -> getVmInstanceUuid, etc.) and return the associated
VM UUID when found; 3) return null for non-VM-related resources. Use existing
DAOs/VOs for lookups and keep the resolver registered so `@MetadataImpact` can
autowire it.

In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java`:
- Around line 28-29: The vmUuids field in CleanupVmInstanceMetadataAction
currently allows empty-string elements because its `@Param` uses emptyString =
true; change the annotation to emptyString = false on the public java.util.List
vmUuids field so SDK validation will reject [""] values (or, if this file is
generated, fix the API message parameter constraint for vmUuids to set
emptyString=false and re-generate the SDK so the generated
CleanupVmInstanceMetadataAction has emptyString=false).

In
`@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java`:
- Around line 28-29: The uuid field in
GetVmInstanceMetadataFromPrimaryStorageAction is marked required=true but still
allows empty strings (nonempty=false, emptyString=true), so update the `@Param` on
the uuid field to reject empty strings (e.g., set nonempty=true and
emptyString=false) so the SDK client-side validation blocks uuid=""; if this
file is generated, instead fix the source API message's `@APIParam` for uuid and
re-generate the SDK.

---

Nitpick comments:
In
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 78-80: The code in CleanupVmInstanceMetadataOnPrimaryStorageGC
sets the `@GC-annotated` field hostUuid to null which may persist that change;
instead, stop mutating the field and use a local variable for the temporary "no
longer exists" state and subsequent logic. Locate the hostUuid usage in the
class (field hostUuid and the block that logs and sets hostUuid = null),
introduce a local variable (e.g., localHostUuid) to hold the nullable value for
this GC run, leave the hostUuid field untouched, and update any subsequent
references in this method to use the local variable so no persistent `@GC` field
is modified.
- Line 50: The query at String psType = Q.New(PrimaryStorageVO.class)... is
using PrimaryStorageAO_ for the generated metamodel but the existence check
above uses PrimaryStorageVO.class, causing inconsistency; change the metamodel
references to PrimaryStorageVO_ so the select and eq use PrimaryStorageVO_.type
and PrimaryStorageVO_.uuid (i.e., update the Q.New(...).select(...) and .eq(...)
calls to reference PrimaryStorageVO_ instead of PrimaryStorageAO_) to keep the
type/VO usage consistent with the earlier check in
CleanupVmInstanceMetadataOnPrimaryStorageGC.

In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Line 56: VmExpungeMetadataFlow.java 中第56行存在多余空行,删除该空行以保持代码格式一致;定位到类
VmExpungeMetadataFlow(或相关方法体)并移除该多余的空白行即可,不影响逻辑或其他符号。
- Around line 102-103: 在 VmExpungeMetadataFlow 中消除冗余的 final 变量:不要创建 finalPsUuid
和 finalHostUuid 两个中间变量,改为直接在 lambda/匿名类中使用已是 effectively final 的 psUuid 或将
hostUuid 的赋值逻辑提前使其成为 effectively final;如果必须显式固定值,保留单个局部 final 变量(例如 finalPsUuid
或 finalHostUuid 之一)并移除另一个,确保引用的变量在创建 lambda
之后不再被修改(参考符号:psUuid、hostUuid、finalPsUuid、finalHostUuid)。

In
`@header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy`:
- Around line 7-66: 所有 field 的 desc 目前为空,按 API 文档要求请为每个字段补充中文描述以说明含义;在
VmMetadataScanEntryDoc_zh_cn.groovy 中更新各 field 条目(比如 vmUuid, vmName, vmCategory,
architecture, schemaVersion, metadataPath, hostUuid, sizeBytes, lastUpdateTime,
incomplete)将 desc 从 "" 替换为简短中文说明(例如 vmUuid: "虚拟机 UUID", vmName: "虚拟机名称",
sizeBytes: "元数据文件大小(字节)", incomplete: "元数据是否不完整" 等),保持风格一致并在必要时补充单位或取值说明以便文档清晰。

In
`@header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy`:
- Around line 5-23: The doc is missing the business response fields declared in
APIUpdateVmInstanceMetadataEvent; add field entries for totalCleaned (integer,
总共清理的条目数), totalFailed (integer, 清理失败的条目数) and failedVmUuids (list/string array,
清理失败的云主机UUID列表) in APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy alongside
the existing success and error blocks so the generated API docs reflect the
actual event properties; use the same "since" version as the file (5.0.0) and
include appropriate types and descriptions matching the Java fields in
APIUpdateVmInstanceMetadataEvent.

In `@header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java`:
- Around line 11-27: The Javadoc says resolver, field, and updateOnFailure are
"Required" but they have defaults; update the annotation consumer to enforce the
requirement when Impact != NONE (or specifically when impact is CONFIG or
STORAGE): when processing MetadataImpact, validate that resolver() and field()
are non-empty (and explicitly handle updateOnFailure if your logic needs an
explicit value) and throw a clear configuration/validation exception if missing;
alternatively, if you prefer keeping silent defaults, update the Javadoc on
resolver(), field(), and updateOnFailure() to state they are optional and
describe the default behavior instead of saying "Required".

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java`:
- Around line 3-11: 为 VmMetadataPathBuildExtensionPoint 接口的每个方法添加 Javadoc 注释:对
getPrimaryStorageType、buildVmMetadataPath、buildMetadataDir、validateMetadataPath
和 requireHostForCleanup 逐个说明用途、参数含义(例如
primaryStorageUuid、vmInstanceUuid)、返回值语义(明确 validateMetadataPath 在成功/失败时返回什么——例如
null/空串或错误描述)、以及路径格式约定和任何约束(如是否包含前缀/后缀、分隔符规则);保持注释简洁明确以便实现者和调用方理解各方法契约。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java`:
- Around line 6-15: Add Javadoc to the VmMetadataResourcePersistExtensionPoint
interface and to each method (getPrimaryStorageType, afterVolumePersist,
afterSnapshotPersist, afterRegistrationRollback) describing purpose, parameter
semantics (explain primaryStorageUuid, resourceUuid, resourceType meaning,
hostUuid, size units, and what Timestamp now represents), and the rollback
semantics for afterRegistrationRollback (what callers should expect when it's
invoked and whether implementors must be idempotent). Also ensure no redundant
access modifiers are used on the interface methods (they should remain
package-private syntax-wise as in interface defaults) and keep the Javadoc
concise and consistent with project coding guidelines.

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 10-13: In VmUuidFromApiResolver, avoid allocating the ArrayList
when fieldValues is null or empty by moving the List<String> result = new
ArrayList<>(); declaration to after the null/empty check; specifically, in the
method that currently creates result before "if (fieldValues == null ||
fieldValues.isEmpty()) { return result; }", change the flow so you first return
an empty list (Collections.emptyList() or new ArrayList<>()) on the null/empty
check and only allocate the mutable result list when you know fieldValues
contains elements that must be processed.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 918-962: The two nested classes PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp use fully qualified parent types
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse;
change their declarations to extend the inner types directly (AgentCommand and
AgentResponse) to match the style of the other DTOs in LocalStorageKvmBackend
(e.g., use "class PrefixRebaseBackingFilesCmd extends AgentCommand" and "class
PrefixRebaseBackingFilesRsp extends AgentResponse"), keeping all fields and
behavior unchanged.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 38-54: Both buildMetadataDir and buildVmMetadataPath repeat the
same PrimaryStorageVO URL query and null check; extract a private helper (e.g.,
getPrimaryStorageUrl(String primaryStorageUuid)) that performs
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(...).findValue()
and throws the CloudRuntimeException if null, then have buildMetadataDir and
buildVmMetadataPath call this helper and reuse normalizeBaseDir and
VmInstanceMetadataConstants to build their return values.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1994-1998: Remove the unnecessary intermediate variable
finalHostUuid by introducing a single final/local capture for the lambda and
using it in the containment check; e.g. create a final String candidate =
hostUuid (or final String host = hostUuid) and use
connectedHosts.stream().anyMatch(h -> h.getUuid().equals(candidate)) in the if
condition, then assign hostUuid = connectedHosts.get(0).getUuid() when needed;
apply the same simplification to the identical pattern around the code that
currently lives at lines ~2048-2051 so both checks use a single captured local
instead of two variables.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java`:
- Around line 96-104: The new handle method signatures in
NfsPrimaryStorageBackend
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg...),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg...),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...),
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)) are inconsistent with the
existing handle pattern that accepts (PrimaryStorageInventory inv, msg,
completion); update the interface so these metadata-related handles either
follow the same signature order and include PrimaryStorageInventory as the first
parameter, or if the omission of PrimaryStorageInventory is intentional, add a
clear Javadoc comment on each of the five methods explaining why we use (msg,
hostUuid, completion) and document the design rationale; ensure the change is
applied to the method declarations in NfsPrimaryStorageBackend and any
implementing classes to keep the API consistent.

In `@sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java`:
- Line 3: The import of VmInstanceInventory is redundant because
VmInstanceInventory and the class RegisterVmInstanceFromMetadataResult are in
the same package (org.zstack.sdk); remove the line importing VmInstanceInventory
from RegisterVmInstanceFromMetadataResult so the class relies on the package
scope instead of an unnecessary explicit import.

In
`@sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java`:
- Around line 5-12: The vmInstanceMetadata field and its accessor methods in
ScanVmInstanceMetadataFromPrimaryStorageResult use a raw java.util.List; change
the field declaration and the signatures of setVmInstanceMetadata and
getVmInstanceMetadata to use a parameterized type (e.g.,
java.util.List<VmMetadataScanEntry>) to restore generic type safety, updating
the types in the field name vmInstanceMetadata and the methods
setVmInstanceMetadata and getVmInstanceMetadata accordingly; if
VmMetadataScanEntry is in a different package, import or fully qualify it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3dbeaeef-fd7a-4620-9cce-ec13376c0727

📥 Commits

Reviewing files that changed from the base of the PR and between c7c24cf and 7cdb7d7.

⛔ Files ignored due to path filters (5)
  • conf/globalConfig/vm.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/primaryStorage.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmInstance.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
📒 Files selected for processing (145)
  • compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
  • compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
  • compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
  • header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
  • header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
  • header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceState.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
  • header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO_.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
  • header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
  • header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
  • header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
  • header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
  • header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
  • sdk/src/main/java/SourceClassMap.java
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
  • sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java
  • sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
  • sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
  • sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
  • sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
  • storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
  • testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
  • testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
  • testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy

Comment on lines +1360 to +1362
String error = ext.validateMetadataPath(psUuid, path);
if (error != null) {
throw new ApiMessageInterceptionException(argerr(error));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Platform.argerr definition ==="
fd 'Platform.java' -x rg -n -C4 '\bargerr\s*\(|String\.format' {}

echo
echo "=== validateMetadataPath implementations ==="
rg -n -C4 --glob '*.java' '\bvalidateMetadataPath\s*\('

Repository: MatheMatrix/zstack

Length of output: 21308


🏁 Script executed:

# 查找 Platform.java 中的 err() 方法定义和完整的 argerr() 方法
rg -n -A 15 'private static.*err\(|public static.*err\(' compute/src/main/java/org/zstack/compute/vm/../../../core/src/main/java/org/zstack/core/Platform.java 2>/dev/null || rg -n -A 15 'private static.*err\(|public static.*err\(' -g '**Platform.java'

Repository: MatheMatrix/zstack

Length of output: 5667


🏁 Script executed:

# 查找 validateMetadataPath 的具体实现
rg -n -B 2 -A 8 'validateMetadataPath\s*\(' --type java

Repository: MatheMatrix/zstack

Length of output: 5318


🏁 Script executed:

# 更直接的方法:查找 Platform.java 文件位置并读取相关部分
fd 'Platform.java' --type f | head -5

Repository: MatheMatrix/zstack

Length of output: 174


argerr(error) 会将动态错误消息当作格式字符串,需要改为 argerr("%s", error) 以安全处理。

error 包含 % 字符时(例如扩展返回包含用户输入 metadataPath 的错误消息),调用 String.format(error, {}) 会抛出 MissingFormatArgumentException 而非参数校验错误。查证表明 argerr() 最终通过 err() 方法中的 toI18nString(fmt, args) 调用 String.format() 处理第一个参数作为格式字符串。

虽然当前实现返回静态错误消息,但接口设计允许扩展返回包含用户输入的动态错误消息,因此应采用 argerr("%s", error) 的安全写法,将 error 作为参数传递而非格式字符串。

🤖 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/VmInstanceApiInterceptor.java`
around lines 1360 - 1362, The current call throws
ApiMessageInterceptionException using argerr(error) which treats the dynamic
error string as a format pattern; change the throw site (the block that calls
ext.validateMetadataPath(psUuid, path) and throws new
ApiMessageInterceptionException(...)) to pass the message as an argument, e.g.
use argerr("%s", error) so the returned error is safely formatted as data rather
than a format string; keep the rest of the logic (checking error != null and
throwing ApiMessageInterceptionException) unchanged.

import org.zstack.header.other.APIMultiAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.vm.metadata.MetadataImpact;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证 NicUuidToVmUuidResolver 是否已实现
rg -n "class NicUuidToVmUuidResolver" --type=java
rg -n "NicUuidToVmUuidResolver" --type=java -l

Repository: MatheMatrix/zstack

Length of output: 349


🏁 Script executed:

# 搜索所有resolver实现
find . -name "*Resolver*.java" -type f | head -20

# 搜索MetadataImpact注解的使用示例
rg "@MetadataImpact" --type=java -A 3 | head -50

# 检查是否有resolver实现的base class或interface
rg "class.*Resolver" --type=java | grep -i "resolver"

Repository: MatheMatrix/zstack

Length of output: 6479


🏁 Script executed:

# 查找元数据resolver相关的实现
find . -path "*/metadata/*" -name "*.java" -type f | grep -i resolver

# 查找resolver接口或基类
rg "interface.*Resolver|abstract class.*Resolver|class.*Resolver" --type=java | grep -v "test\|Test"

# 搜索resolver注册或工厂机制
rg "NicUuidToVmUuidResolver\|resolverClass\|getResolver" --type=java | head -30

Repository: MatheMatrix/zstack

Length of output: 650


🏁 Script executed:

# 查看VmUuidFromApiResolver接口定义
cat -n header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java

# 搜索VmUuidFromApiResolver的实现类
rg "implements VmUuidFromApiResolver" --type=java

# 在compute包中搜索resolver相关实现
find compute -name "*Resolver*.java" -type f

# 搜索所有metadata相关的resolver
find . -path "*/metadata/*" -type f -name "*.java"

Repository: MatheMatrix/zstack

Length of output: 2530


🏁 Script executed:

# 查看MetadataImpact注解定义
cat -n header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java

# 在整个compute目录中搜索任何Nic或resolver相关的类
find compute -name "*.java" -type f | xargs grep -l "NicUuidToVmUuidResolver\|VmUuidFromApiResolver" 2>/dev/null | head -10

# 搜索@Component或@Bean注解且包含resolver的类
rg "@Component|@Bean" compute --type=java -A 2 | grep -i "resolver" -B 2 -A 2 | head -30

Repository: MatheMatrix/zstack

Length of output: 1172


🏁 Script executed:

# 搜索所有VmUuidFromApiResolver的实现
rg "class.*implements.*VmUuidFromApiResolver|class.*VmUuidFromApiResolver" --type=java -A 3

# 在compute包中搜索resolver的注册或bean定义
find compute -name "*.java" -type f | xargs grep -l "@Component\|@Bean\|@Service" | xargs grep -l -i "resolver" | head -5

# 搜索所有实现了resolver接口的类
rg "implements VmUuidFromApiResolver\|extends.*Resolver" --type=java -B 2 -A 5 | head -50

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 搜索所有包含resolver的配置文件和Spring注册
find . -type f \( -name "*.xml" -o -name "*.properties" -o -name "*.yml" -o -name "*.yaml" \) | xargs grep -l "resolver" 2>/dev/null | head -10

# 搜索任何resolver相关的factory或registry
rg "resolver.*factory|registry.*resolver|ResolverFactory" --type=java -i | head -20

# 检查是否有resolver的自动发现或初始化机制
rg "getBean\(.*resolver|resolverMap\|resolverRegistry" --type=java -i | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查看APIChangeVmNicStateMsg.java完整内容
cat header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java

# 搜索所有提到NicUuidToVmUuidResolver的地方
rg "NicUuidToVmUuidResolver" --type=java -B 2 -A 2

# 检查是否有任何Nic相关的resolver定义(不同命名)
find compute -name "*Nic*.java" -type f | xargs grep -l "VmUuidFromApiResolver\|Resolver" 2>/dev/null | head -10

Repository: MatheMatrix/zstack

Length of output: 5077


缺失 NicUuidToVmUuidResolver Spring Bean 实现。

@MetadataImpact 注解指定的 resolver = "NicUuidToVmUuidResolver" 在整个代码库中不存在。根据 MetadataImpact 注解定义,resolver 参数值应为已注册的 Spring Bean ID,且该 Bean 必须实现 VmUuidFromApiResolver 接口。

当前有 4 个 API 消息类引用此不存在的 resolver:

  • APIChangeVmNicStateMsg.java
  • APIDetachL3NetworkFromVmMsg.java
  • APIUpdateVmNicDriverMsg.java
  • APIChangeVmNicNetworkMsg.java

需要实现 NicUuidToVmUuidResolver 并将其注册为 Spring Bean,以解析网卡 UUID 到虚拟机 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/vm/APIChangeVmNicStateMsg.java` at
line 12, Implement a Spring bean named "NicUuidToVmUuidResolver" that implements
the VmUuidFromApiResolver interface and resolves a NIC UUID to its VM UUID;
create a class (e.g., NicUuidToVmUuidResolver) implementing
VmUuidFromApiResolver, inject the appropriate NIC/VM lookup service or DAO used
elsewhere in the project, implement the resolver method to fetch the VM UUID by
the given nicUuid (handling not-found and errors consistently with other
resolvers), and register it as a Spring bean with the id
"NicUuidToVmUuidResolver" so that the
`@MetadataImpact`(resolver="NicUuidToVmUuidResolver") usages in
APIChangeVmNicStateMsg, APIDetachL3NetworkFromVmMsg, APIUpdateVmNicDriverMsg and
APIChangeVmNicNetworkMsg resolve correctly.


clz APIGetVmInstanceMetadataFromPrimaryStorageMsg.class

desc """"""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

补充请求 desc 内容,避免生成空说明。

Line 20 当前是空字符串,建议填入最小语义描述,避免 API 文档出现空白说明。

✏️ 建议修改
-			desc """"""
+			desc """从主存储读取指定云主机元数据"""
📝 Committable suggestion

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

Suggested change
desc """"""
desc """从主存储读取指定云主机元数据"""
🤖 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/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy`
at line 20, 在 APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中的
desc """""" 为空,会在生成的 API 文档中留下空白说明;请用一条最小语义的中文描述替换该空字符串,例如说明此 API
获取虚拟机在主存储上的元数据(或类似简短句子),以便在类/文件
APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中的 desc
字段有实际内容并避免空说明。

Comment on lines +8 to +10
String getPrimaryStorageType();
PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);
class PathReplacementResult {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

为扩展点接口方法补充 Javadoc。

Line 8-9 的接口方法缺少注释,后续实现方难以统一语义与输入输出约束。

📝 建议修改
 public interface VmMetadataPathReplacementExtensionPoint {
+    /**
+     * `@return` 当前扩展实现支持的主存储类型标识
+     */
     String getPrimaryStorageType();
+
+    /**
+     * 计算 metadata 原路径到当前路径的替换信息。
+     *
+     * `@param` targetPsUuid 目标主存储 UUID
+     * `@param` allOldPaths  待处理的历史 metadata 路径集合
+     * `@return` 替换结果(映射与前缀替换信息)
+     */
     PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);
As per coding guidelines "接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。"
📝 Committable suggestion

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

Suggested change
String getPrimaryStorageType();
PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);
class PathReplacementResult {
/**
* `@return` 当前扩展实现支持的主存储类型标识
*/
String getPrimaryStorageType();
/**
* 计算 metadata 原路径到当前路径的替换信息
*
* `@param` targetPsUuid 目标主存储 UUID
* `@param` allOldPaths 待处理的历史 metadata 路径集合
* `@return` 替换结果映射与前缀替换信息
*/
PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);
class PathReplacementResult {
🤖 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/metadata/VmMetadataPathReplacementExtensionPoint.java`
around lines 8 - 10, Add Javadoc for the interface methods
getPrimaryStorageType() and calculatePathReplacements(String targetPsUuid,
List<String> allOldPaths) describing their purpose, expected inputs and outputs,
null/empty handling, exception behavior and any concurrency or lifecycle
constraints; also document the nested PathReplacementResult class fields and
meaning (e.g., mapping semantics, order guarantees). Ensure the Javadoc follows
project conventions (brief summary + `@param/`@return/@throws where applicable)
and do not add extra modifiers to the interface methods.

Comment on lines +7 to +24
String resolveVmUuid(String fieldValue);

default List<String> batchResolveVmUuids(List<String> fieldValues) {
List<String> result = new ArrayList<>();
if (fieldValues == null || fieldValues.isEmpty()) {
return result;
}
for (String v : fieldValues) {
if (v == null) {
continue;
}
String vmUuid = resolveVmUuid(v);
if (vmUuid != null) {
result.add(vmUuid);
}
}
return result;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

请为接口方法补充 Javadoc 注释。

当前两个接口方法都缺少 Javadoc,不符合仓库 Java 接口规范,后续会影响 API 意图可读性与文档生成一致性。

✍️ 建议修改
 public interface VmUuidFromApiResolver {
+    /**
+     * 将 API 字段值解析为虚拟机 UUID。
+     *
+     * `@param` fieldValue API 传入字段值
+     * `@return` 解析得到的 VM UUID;无法解析时返回 null
+     */
     String resolveVmUuid(String fieldValue);
 
+    /**
+     * 批量解析 API 字段值为虚拟机 UUID 列表。
+     *
+     * `@param` fieldValues API 字段值列表
+     * `@return` 解析成功的 VM UUID 列表(不包含 null)
+     */
     default List<String> batchResolveVmUuids(List<String> fieldValues) {
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/vm/metadata/VmUuidFromApiResolver.java`
around lines 7 - 24, 为接口 VmUuidFromApiResolver 的两个方法 resolveVmUuid 和
batchResolveVmUuids 添加完整的 Javadoc:在 resolveVmUuid 上说明方法用途(将 API 字段值解析为 VM
UUID)、参数说明 (`@param` fieldValue 允许为 null 或空时的行为)、返回值说明 (`@return` 返回解析出的 VM UUID 或
null);在 batchResolveVmUuids 上说明批量解析行为、对 null 或空集合以及单个 null 元素的处理规则、返回值描述(返回解析成功的
VM UUID 列表,保持输入顺序或说明不会包含 nulls);遵守接口规范不要为接口方法添加多余修饰符(如 public)。

Comment on lines +151 to +168
private void createResourceRef(String primaryStorageUuid, String resourceUuid,
String resourceType, String hostUuid, long size, Timestamp now) {
boolean exists = Q.New(LocalStorageResourceRefVO.class).eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid).isExists();
if (exists) {
logger.debug(String.format("LocalStorageResourceRefVO for resource[uuid:%s] already exists, skip creation", resourceUuid));
return;
}

LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO();
ref.setPrimaryStorageUuid(primaryStorageUuid);
ref.setResourceUuid(resourceUuid);
ref.setResourceType(resourceType);
ref.setHostUuid(hostUuid);
ref.setSize(size);
ref.setCreateDate(now);
ref.setLastOpDate(now);
dbf.persist(ref);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

createResourceRef 中存在潜在的竞态条件。

第 153-157 行的"先检查后插入"模式存在 TOCTOU(Time-of-Check to Time-of-Use)问题。如果两个并发请求同时通过存在性检查,其中一个会因数据库唯一约束而失败。

建议使用数据库级别的 upsert 或在 dbf.persist() 处捕获约束冲突异常:

🛡️ 建议的防御性修复
 private void createResourceRef(String primaryStorageUuid, String resourceUuid,
                                String resourceType, String hostUuid, long size, Timestamp now) {
     boolean exists = Q.New(LocalStorageResourceRefVO.class).eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid).isExists();
     if (exists) {
         logger.debug(String.format("LocalStorageResourceRefVO for resource[uuid:%s] already exists, skip creation", resourceUuid));
         return;
     }

     LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO();
     ref.setPrimaryStorageUuid(primaryStorageUuid);
     ref.setResourceUuid(resourceUuid);
     ref.setResourceType(resourceType);
     ref.setHostUuid(hostUuid);
     ref.setSize(size);
     ref.setCreateDate(now);
     ref.setLastOpDate(now);
-    dbf.persist(ref);
+    try {
+        dbf.persist(ref);
+    } catch (Exception e) {
+        // Handle duplicate key exception in case of race condition
+        if (Q.New(LocalStorageResourceRefVO.class).eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid).isExists()) {
+            logger.debug(String.format("LocalStorageResourceRefVO for resource[uuid:%s] created by concurrent call, skip", resourceUuid));
+        } else {
+            throw e;
+        }
+    }
 }
🤖 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/LocalStorageVmMetadataExtension.java`
around lines 151 - 168, The createResourceRef method in
LocalStorageVmMetadataExtension has a TOCTOU race: the exists check
(Q.New(...).isExists()) can race and dbf.persist(ref) may throw a
unique-constraint/duplicate-key error; replace the check-with-insert pattern
with a safe upsert or wrap dbf.persist(ref) in a try/catch to handle constraint
violations gracefully (detect the duplicate-key/constraint exception thrown by
the ORM/DB and treat it as a no-op), or use a DB-level upsert function if
available so concurrent callers won't fail; update createResourceRef to catch
the specific persistence exception and log/debug that the ref already exists
instead of letting the error propagate.

Comment on lines +136 to +141
public static final String WRITE_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/write";

public static final String GET_VM_INSTANCE_METADATA_PATH = "/nfsprimarystorage/vm/metadata/get";
public static final String SCAN_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/scan";
public static final String CLEANUP_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/cleanup";
public static final String NFS_PREFIX_REBASE_BACKING_FILES_PATH = "/nfsprimarystorage/snapshot/prefixrebasebackingfiles";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

paths=(
  "/nfsprimarystorage/vm/metadata/write"
  "/nfsprimarystorage/vm/metadata/get"
  "/nfsprimarystorage/vm/metadata/scan"
  "/nfsprimarystorage/vm/metadata/cleanup"
  "/nfsprimarystorage/snapshot/prefixrebasebackingfiles"
)

for p in "${paths[@]}"; do
  echo "== $p =="
  rg -n -C2 --fixed-strings "$p" \
    -g '!**/target/**' \
    -g '!**/build/**' \
    -g '!**/out/**'
done

Repository: MatheMatrix/zstack

Length of output: 4995


🏁 Script executed:

# 搜索 NfsPrimaryStorage 相关的 agent 处理代码
rg -n "nfsprimarystorage" \
  -g '!**/target/**' \
  -g '!**/build/**' \
  -g '!**/out/**' \
  --type java | head -50

Repository: MatheMatrix/zstack

Length of output: 11287


🏁 Script executed:

# 搜索是否有这些常量被使用的地方(除了定义)
rg -n "WRITE_VM_METADATA_PATH|GET_VM_INSTANCE_METADATA_PATH|SCAN_VM_METADATA_PATH|CLEANUP_VM_METADATA_PATH|NFS_PREFIX_REBASE_BACKING_FILES_PATH" \
  -g '!**/target/**' \
  -g '!**/build/**' \
  -g '!**/out/**' \
  --type java

Repository: MatheMatrix/zstack

Length of output: 4466


🏁 Script executed:

# 搜索 KVM agent 中的路径处理 handler
rg -n "metadata" \
  -g '!**/target/**' \
  -g '!**/build/**' \
  -g '!**/out/**' \
  --type java | grep -i "kvm\|agent\|handler" | head -30

Repository: MatheMatrix/zstack

Length of output: 4908


🏁 Script executed:

# 搜索 KVM agent 模块(不是 simulator)
find . -path "**/target" -prune -o -path "**/build" -prune -o \
  -type f -name "*.java" -print | xargs grep -l "RequestMapping\|@PostMapping" | grep -i kvm | grep -v simulator | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查找哪里实现了 /nfsprimarystorage 路由处理
rg -n "@RequestMapping|@PostMapping|@GetMapping" \
  -g '!**/target/**' \
  -g '!**/build/**' \
  --type java -A1 | grep -B1 "nfsprimarystorage"

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查找 KVM host agent 代码,看是否有新路径的处理
find . -path "**/target" -prune -o -path "**/build" -prune -o \
  -type f -name "KVMHost.java" -print | head -5

Repository: MatheMatrix/zstack

Length of output: 116


🏁 Script executed:

# 查看 KVMHost.java 大小和内容,查找 metadata 相关的处理器
wc -l plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java

Repository: MatheMatrix/zstack

Length of output: 119


🏁 Script executed:

# 在 KVMHost.java 中搜索 metadata 相关代码
rg -n "metadata|/nfsprimarystorage" \
  plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查找注册 handler 或 route 的代码位置
rg -n "RequestMapping\|addPath\|registerHandler\|addRoute" \
  plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 在整个 agent 代码中搜索这些新 path 是否有处理
rg -n "WRITE_VM_METADATA_PATH|GET_VM_INSTANCE_METADATA_PATH|SCAN_VM_METADATA_PATH|CLEANUP_VM_METADATA_PATH|NFS_PREFIX_REBASE_BACKING_FILES_PATH" \
  plugin/kvm/ \
  -g '!**/target/**' \
  -g '!**/build/**'

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查找 NFS 相关的 agent 处理代码(如果存在)
find plugin/kvm -name "*nfs*" -o -name "*Nfs*" 2>/dev/null | head -10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查看 KVMHost.java 中是否有 POST handler 或类似的路由注册
grep -n "POST\|handler\|route" plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java | head -30

Repository: MatheMatrix/zstack

Length of output: 230


确认生产态 KVM agent 尚未同步注册这 5 个新 path,合入前必须完成 agent 侧实现。

管理节点已在发送这些新 path 的请求(NfsPrimaryStorageKVMBackend lines 2078/2108/2140/2176/2210),但搜索结果表明 KVM agent 代码中完全没有对应的处理器。这将在生产环境导致运行时 404 错误。

  • /nfsprimarystorage/vm/metadata/write
  • /nfsprimarystorage/vm/metadata/get
  • /nfsprimarystorage/vm/metadata/scan
  • /nfsprimarystorage/vm/metadata/cleanup
  • /nfsprimarystorage/snapshot/prefixrebasebackingfiles

需要在 KVM agent 侧(plugin/kvm)添加这 5 个 path 的处理器注册。参考 LocalStorageSimulator/LocalStorageKvmBackend 的实现模式,确保 agent 能正确处理这些请求。

🤖 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 136 - 141, NfsPrimaryStorageKVMBackend adds five new request paths
(WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH,
CLEANUP_VM_METADATA_PATH, NFS_PREFIX_REBASE_BACKING_FILES_PATH) but the KVM
agent has no registered handlers for them; add corresponding handler
registrations in the KVM agent (plugin/kvm) following the pattern used by
LocalStorageSimulator/LocalStorageKvmBackend: implement request processors for
each path that deserialize the incoming request, perform the same semantics
expected by NfsPrimaryStorageKVMBackend, and register them with the agent’s HTTP
dispatcher so the agent will not return 404s for these endpoints.

import org.zstack.header.message.APIDeleteMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.rest.RestRequest;
import org.zstack.header.vm.metadata.MetadataImpact;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证 ResourceUuidToVmUuidResolver 的实现
rg -n "class ResourceUuidToVmUuidResolver" --type=java -A 20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Find the APIDeleteResourceConfigMsg file
fd -t f "APIDeleteResourceConfigMsg.java"

Repository: MatheMatrix/zstack

Length of output: 148


🏁 Script executed:

# Search for ResourceUuidToVmUuidResolver anywhere in codebase
rg "ResourceUuidToVmUuidResolver" --type=java

Repository: MatheMatrix/zstack

Length of output: 860


🏁 Script executed:

# Search for similar resolver patterns
rg "Resolver.*class" --type=java -A 5 | head -100

Repository: MatheMatrix/zstack

Length of output: 809


🏁 Script executed:

# Look for `@MetadataImpact` annotation usage to understand the pattern
rg "@MetadataImpact" --type=java -B 2 -A 3 | head -150

Repository: MatheMatrix/zstack

Length of output: 17219


🏁 Script executed:

# Search for where resolver classes are defined/implemented
rg "class.*Resolver.*{" --type=java -B 2 | head -200

Repository: MatheMatrix/zstack

Length of output: 161


🏁 Script executed:

# Check if there's a resolver registry or factory
rg "ResourceUuidToVmUuidResolver\|VolumeUuidToVmUuidResolver\|VmUuidDirectResolver" --type=java | grep -v "@MetadataImpact" | head -50

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Look for resolver implementations
fd -type f -name "*Resolver*.java" | head -30

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

# Search for resolver class definitions (simpler pattern)
rg "class.*Resolver" --type=java -B 1 -A 3 | head -150

Repository: MatheMatrix/zstack

Length of output: 1718


🏁 Script executed:

# Find all resolver files
fd ".*Resolver.*\.java$"

Repository: MatheMatrix/zstack

Length of output: 139


🏁 Script executed:

# Check header directory for MetadataImpact related infrastructure
rg "MetadataImpact" --type=java | grep -v "@MetadataImpact" | head -50

Repository: MatheMatrix/zstack

Length of output: 6401


🏁 Script executed:

# Read the MetadataImpact interface to understand resolver expectations
cat header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java

Repository: MatheMatrix/zstack

Length of output: 934


🏁 Script executed:

# Search for resolver implementations in the metadata directory
fd -type f -name "*.java" | xargs rg "Resolver" | grep -i "metadata\|resource" | head -50

Repository: MatheMatrix/zstack

Length of output: 9542


🏁 Script executed:

# Look for all resolver implementations
rg "implements.*Resolver\|extends.*Resolver" --type=java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Look for VmUuidFromApiResolver interface
cat header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java

Repository: MatheMatrix/zstack

Length of output: 738


🏁 Script executed:

# Search for actual implementations of VmUuidFromApiResolver
rg "implements VmUuidFromApiResolver" --type=java -B 2 -A 10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search in resourceconfig directory for resolver
fd -type f "*.java" resourceconfig/ | xargs rg "ResourceUuidToVmUuidResolver" 2>/dev/null

Repository: MatheMatrix/zstack

Length of output: 1048


🏁 Script executed:

# Search for all implementations of VmUuidFromApiResolver
rg "implements VmUuidFromApiResolver"

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search for bean configurations that might define ResourceUuidToVmUuidResolver
rg "ResourceUuidToVmUuidResolver" --type=java -B 5 -A 5

Repository: MatheMatrix/zstack

Length of output: 5589


🏁 Script executed:

# Look for Spring bean definitions in resourceconfig
fd -type f "*.java" resourceconfig/ | xargs rg "@Component\|@Bean\|@Service" 2>/dev/null | head -30

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

# Search for where resolvers are actually implemented/defined
fd -type f "*.java" | xargs grep -l "VolumeUuidToVmUuidResolver\|VmUuidDirectResolver\|NicUuidToVmUuidResolver" | head -10

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

# Check compute module for resolver implementations
fd -type f "*.java" compute/ | xargs rg "class.*Resolver.*implements" 2>/dev/null | head -20

Repository: MatheMatrix/zstack

Length of output: 232


🏁 Script executed:

# Search for `@Component` or configuration that registers resolvers
rg "@Component.*Resolver\|@Bean.*Resolver" -A 5 | head -50

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search for any file containing actual resolver implementations
find . -name "*.java" -type f -exec grep -l "class.*Resolver.*implements VmUuidFromApiResolver" {} \;

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Look for Spring component scan or bean registration for resolvers
find . -name "*.java" -type f -exec grep -l "ResourceUuidToVmUuidResolver\|VolumeUuidToVmUuidResolver" {} \; | head -20

Repository: MatheMatrix/zstack

Length of output: 1343


🏁 Script executed:

# Search in all java files for resolver pattern
find . -name "*.java" -type f -exec grep -H "class.*Resolver" {} \; | grep -i "uuid\|resolver" | head -30

Repository: MatheMatrix/zstack

Length of output: 457


🏁 Script executed:

# Search for Spring `@Component` definitions with resolver names
find . -name "*.java" -type f | xargs grep -l "@Component.*ResourceUuidToVmUuidResolver\|@Component.*Resolver" 2>/dev/null | head -10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Look for configuration classes that might define these beans
find . -name "*.java" -type f -path "*config*" | xargs grep -l "ResourceUuidToVmUuidResolver\|VolumeUuidToVmUuidResolver" 2>/dev/null

Repository: MatheMatrix/zstack

Length of output: 239


🏁 Script executed:

# Check the entire resourceconfig module structure
find resourceconfig/ -name "*.java" -type f | head -30

Repository: MatheMatrix/zstack

Length of output: 2634


🏁 Script executed:

# Look at the resourceconfig module for any configuration or bean definitions
find resourceconfig/ -name "*.java" -type f -exec grep -l "@Component\|@Bean\|@Configuration" {} \;

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search across the entire codebase for implementations of VmUuidFromApiResolver
find . -name "*.java" -type f -exec grep -l "implements VmUuidFromApiResolver" {} \;

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if there are any XML or property files that might define beans
find . -name "*.xml" -o -name "*.properties" | xargs grep -l "ResourceUuidToVmUuidResolver" 2>/dev/null

Repository: MatheMatrix/zstack

Length of output: 46


🏁 Script executed:

# Look for any recent resolver implementations
find . -name "*.java" -type f -exec grep -H "VolumeUuidToVmUuidResolver\|VmUuidDirectResolver" {} \; | grep "class\|@Component\|@Bean" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Get the raw count of implementations
find . -name "*.java" -type f | xargs grep "implements VmUuidFromApiResolver"

Repository: MatheMatrix/zstack

Length of output: 46


🏁 Script executed:

# Search for any Spring bean registration or configuration that might use string-based bean names
find . -name "*.java" -type f | xargs grep -i "bean\|resolver" | grep -i "resource.*uuid\|volume.*uuid" | head -40

Repository: MatheMatrix/zstack

Length of output: 5220


🏁 Script executed:

# Check compute module for any resolver implementations
find compute/ -name "*.java" -type f | xargs grep "VmUuidFromApiResolver\|Resolver" | head -50

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search the entire codebase one more time with simpler pattern
find . -type f -name "*.java" | xargs grep -h "class.*Resolver" | grep -v "^//" | head -50

Repository: MatheMatrix/zstack

Length of output: 261


🏁 Script executed:

# Check if resolvers might be in a separate module or plugin
find . -type d -name "*metadata*" -o -type d -name "*resolver*"

Repository: MatheMatrix/zstack

Length of output: 114


🏁 Script executed:

# Read the APIDeleteResourceConfigMsg file completely to understand context
cat -n resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java

Repository: MatheMatrix/zstack

Length of output: 2180


🏁 Script executed:

# Check metadata directory for all resolver files
find ./header/src/main/java/org/zstack/header/vm/metadata -name "*.java" -type f

Repository: MatheMatrix/zstack

Length of output: 1678


🏁 Script executed:

# Check for any todo or fixme comments related to resolver
find . -type f -name "*.java" | xargs grep -i "todo.*resolver\|fixme.*resolver" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Search in Spring configuration or auto-configuration files
find . -type f -name "*.java" | xargs grep -l "EnableAutoConfiguration\|ComponentScan\|Configuration" | xargs grep -l "metadata\|resolver" | head -10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Final verification: search for any resolver bean definition patterns
find . -type f -name "*.java" | xargs grep -h "new.*Resolver\|bean.*resolver" | grep -i "resourceuuid\|volume" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if maybe resolvers are auto-wired or registered differently
find . -type f -name "*.java" | xargs grep -l "VolumeUuidToVmUuidResolver" | xargs cat | grep -A 20 "class.*VolumeUuidToVmUuidResolver"

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# One more comprehensive check - look for all files that might define these resolvers
find . -type f -name "*.java" | xargs grep "VolumeUuidToVmUuidResolver\|VmUuidDirectResolver" | grep -v "@MetadataImpact" | grep -v "import"

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if resolvers might be registered through a plugin or extension point
find . -type f -name "*.java" | xargs grep -l "PluginRegistry\|ExtensionPointRegistry" | xargs grep -l "Resolver" | head -5

Repository: MatheMatrix/zstack

Length of output: 129


🏁 Script executed:

# Search for any service or manager that handles resolver registration
find . -type f -name "*.java" | xargs grep -l "registerResolver\|getResolver\|resolveVmUuid" | head -10

Repository: MatheMatrix/zstack

Length of output: 141


🏁 Script executed:

# Look for where MetadataImpact annotation is actually processed/used
find . -type f -name "*.java" | xargs grep -l "MetadataImpact" | xargs grep -l "@Transactional\|Interceptor\|Aspect" | head -10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if resolvers might be in a separate artifact or module
find . -name "pom.xml" -type f | xargs grep -l "metadata\|resolver" | head -5

Repository: MatheMatrix/zstack

Length of output: 77


🏁 Script executed:

# One final check - search for any Spring bean definition or factory that might create these resolvers
find . -type f \( -name "*.java" -o -name "*.xml" \) | xargs grep -l "createResolver\|getResolverBean\|resolverRegistry" 2>/dev/null | head -10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if the resolver might be expected to be created but is actually missing
find . -type f -name "*.java" | xargs grep -B 5 -A 5 "ResourceUuidToVmUuidResolver" | head -100

Repository: MatheMatrix/zstack

Length of output: 5674


ResourceUuidToVmUuidResolver 实现缺失,无法处理非 VM 资源。

@MetadataImpact 注解引用了 ResourceUuidToVmUuidResolver 作为 Spring Bean,但在代码库中找不到其实现类。由于 resourceUuid 字段类型为 ResourceVO.class,可能指向任意资源类型(Host、Cluster、PrimaryStorage 等),而非仅限于 VM 关联的资源。需要实现 ResourceUuidToVmUuidResolver 以正确解析以下情况:

  1. resourceUuid 是 VM UUID 时的直接关联
  2. resourceUuid 是与 VM 关联的资源(如 Volume、NIC)时的间接关联
  3. resourceUuid 是与 VM 无关的资源时的处理(应返回 null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java`
at line 8, The project is missing an implementation of
ResourceUuidToVmUuidResolver referenced by the `@MetadataImpact` on
APIDeleteResourceConfigMsg (resourceUuid of type ResourceVO.class); implement a
Spring bean class named e.g. ResourceUuidToVmUuidResolverImpl that implements
ResourceUuidToVmUuidResolver, annotate it as a component/service, and implement
resolve(resourceUuid) to: 1) check if resourceUuid is a VM UUID (query
VmInstanceVO) and return it; 2) check known VM-related resources (VolumeVO ->
getVmInstanceUuid, VmNicVO -> getVmInstanceUuid, etc.) and return the associated
VM UUID when found; 3) return null for non-VM-related resources. Use existing
DAOs/VOs for lookups and keep the resolver registered so `@MetadataImpact` can
autowire it.

Comment on lines +28 to +29
@Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
public java.util.List vmUuids;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

vmUuids 仍然接受空字符串元素。

nonempty = true 只保证列表非空,但 emptyString = true 仍允许 [""] 这样的值通过 SDK 参数校验。对于 UUID 列表这里应该直接 fail fast;如果这是生成代码,根因应补到对应 API message 的参数约束后再重新生成。

🤖 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/CleanupVmInstanceMetadataAction.java` around
lines 28 - 29, The vmUuids field in CleanupVmInstanceMetadataAction currently
allows empty-string elements because its `@Param` uses emptyString = true; change
the annotation to emptyString = false on the public java.util.List vmUuids field
so SDK validation will reject [""] values (or, if this file is generated, fix
the API message parameter constraint for vmUuids to set emptyString=false and
re-generate the SDK so the generated CleanupVmInstanceMetadataAction has
emptyString=false).

Comment on lines +28 to +29
@Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
public java.lang.String uuid;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

uuid 被声明成“必填但可为空字符串”。

required = true 配上 nonempty = false / emptyString = true 会让 uuid="" 直接通过 SDK 校验,错误只能推迟到服务端。这里应把空字符串拦在客户端;如果这是生成产物,请回到对应 API message 的 @APIParam 上修正后再生成 SDK。

🤖 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/GetVmInstanceMetadataFromPrimaryStorageAction.java`
around lines 28 - 29, The uuid field in
GetVmInstanceMetadataFromPrimaryStorageAction is marked required=true but still
allows empty strings (nonempty=false, emptyString=true), so update the `@Param` on
the uuid field to reject empty strings (e.g., set nonempty=true and
emptyString=false) so the SDK client-side validation blocks uuid=""; if this
file is generated, instead fix the source API message's `@APIParam` for uuid and
re-generate the SDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants