Skip to content

<feature>[vm]: add vm metadata header definitions#3782

Closed
ZStack-Robot wants to merge 1 commit intozsv_5.0.0from
sync/tao.gan/vm_metadata-ZSV-11559@@3
Closed

<feature>[vm]: add vm metadata header definitions#3782
ZStack-Robot wants to merge 1 commit intozsv_5.0.0from
sync/tao.gan/vm_metadata-ZSV-11559@@3

Conversation

@ZStack-Robot
Copy link
Copy Markdown
Collaborator

Resolves: ZSV-11559

Change-Id: I3cd9fa26b6af7bd47787ab88c5050683a3356059

sync from gitlab !9651

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

添加完整的云主机元数据子系统:新的 REST API、消息/回复、注解、扩展点、数据库表与 JPA 实体、主存储(Local/NFS)后端协议与 agent 命令、相关 SDK/测试支持,以及用于写入/读取/扫描/清理/重基路径的实现与 GC/流程组件。

Changes

Cohort / File(s) Summary
计算 / 流程 / GC
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
新增基于时间的清理 GC、VM 删除时的元数据清理 Flow、VM 元数据相关 GlobalConfig 条目、API 消息拦截校验(RegisterFromMetadata)与系统标签(注册管理节点 UUID)。
数据库 schema 与 JPA 实体
conf/db/zsv/V5.0.0__schema.sql, 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/*_*.java
新增数据库表与 JPA 实体/元模型(VmMetadataDirtyVO、VmMetadataFlushStateVO 等),用于标记脏元数据、刷新状态与恢复计数等持久化字段。
Header:消息 / API / 事件 / DTO / 注解 / 扩展点
header/src/main/java/org/zstack/header/storage/primary/*, header/src/main/java/org/zstack/header/vm/*, header/src/main/java/org/zstack/header/vm/metadata/*
新增大量 primary-storage 消息/回复(Scan/Get/Cleanup/Update/Rebase/Read)、API 消息与事件(Register/Scan/Get/Cleanup/Update 等)、VmMetadata DTO 与 VmMetadataScanEntry、@MetadataImpact 注解及多种扩展点接口(路径构建、替换、持久化、API 解析器等)。
主存储基类与插件适配(Local / NFS)
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java, plugin/localstorage/src/..., plugin/nfsPrimaryStorage/src/...
扩展 PrimaryStorageBase 并在 Local/NFS 插件新增 agent/http 路径、命令/响应类型、后端抽象与实现,支持写/读/扫描/清理元数据及重基路径;Local 实现增加资源引用持久化与回滚删除逻辑。
SDK、测试与模拟器
sdk/src/main/java/org/zstack/sdk/*, testlib/src/main/java/org/zstack/testlib/*, SourceClassMap.java
新增 SDK Action/Result/DTO(Register/Scan/Get/Update/Cleanup 等)、ApiHelper 测试封装、模拟器端点与响应记录、以及 SourceClassMap 映射条目。
广泛注解应用(影响声明)
多个 header/src/main/java/org/zstack/header/** 文件(快照、标签、卷、VM 等)
在大量 API/message 类上添加 @MetadataImpact 注解以声明元数据影响类型(NONE/CONFIG/STORAGE)、解析器与失败时是否更新行为。

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as APIService
    participant DB as Database
    participant Plugin as PluginRegistry
    participant PS as PrimaryStorageService
    participant Host as HostAgent

    Client->>API: POST /vm-instances/metadata/register (metadataPath, psUuid, ...)
    API->>Plugin: resolve VmMetadataPathBuildExtensionPoint(psType)
    Plugin-->>API: extension
    API->>PS: GetVmInstanceMetadataFromPrimaryStorageMsg(psUuid, metadataPath[, hostUuid])
    PS->>Host: HTTP GET_VM_INSTANCE_METADATA_PATH
    Host-->>PS: GetVmInstanceMetadataRsp(metadata)
    PS-->>API: GetVmInstanceMetadataFromPrimaryStorageReply(metadata)
    API->>DB: create VM (state=Registering)
    API->>PS: UpdateVmInstanceMetadataOnPrimaryStorageMsg(write metadata)
    PS->>Host: HTTP WRITE_VM_METADATA_PATH
    Host-->>PS: WriteVmMetadataRsp(success)
    PS-->>API: UpdateVmInstanceMetadataOnPrimaryStorageReply(success)
    API->>DB: update VM state -> Running
    API-->>Client: APIRegisterVmInstanceFromMetadataEvent (inventory)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 叽咕——元数据新花开,
路径铺陈、扫描与清理排队来,
主存储、主机与 API 手牵手,
兔子守着 GC,轻轻删旧载新开怀,
春风拂过,元数据安稳回家。

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/tao.gan/vm_metadata-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: 8

🧹 Nitpick comments (11)
header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java (1)

6-14: 建议统一元数据字段命名以降低接口心智负担。

Line 6 使用 vmMetadata,而同批新增回复中多处使用 metadata。建议在同一元数据 API 家族内统一字段名,减少 SDK/序列化映射分歧。

可选修改示例
 public class ReadVmInstanceMetadataReply extends MessageReply {
-    private String vmMetadata;
+    private String metadata;

-    public String getVmMetadata() {
-        return vmMetadata;
+    public String getMetadata() {
+        return metadata;
     }

-    public void setVmMetadata(String vmMetadata) {
-        this.vmMetadata = vmMetadata;
+    public void setMetadata(String metadata) {
+        this.metadata = metadata;
     }
 }
🤖 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/ReadVmInstanceMetadataReply.java`
around lines 6 - 14, The field and accessors in ReadVmInstanceMetadataReply
(vmMetadata, getVmMetadata, setVmMetadata) use a different name than other
replies that use metadata; rename the field and its getters/setters to metadata
(or vice versa to match the majority) so the DTO/serialization mapping is
consistent, update all usages and imports that reference
vmMetadata/getVmMetadata/setVmMetadata, and add/adjust any
JSON/XML/serialization annotations (e.g., `@JsonProperty`) on the field or methods
if renaming would break external contracts to preserve wire compatibility.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java (1)

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

当前扩展点方法缺少契约说明,后续实现方难以统一理解参数语义与调用时机。

♻️ 建议修改
 public interface VmMetadataResourcePersistExtensionPoint {
+    /**
+     * `@return` 支持该扩展的主存储类型
+     */
     String getPrimaryStorageType();
 
+    /**
+     * 卷元数据持久化完成后的回调。
+     */
     void afterVolumePersist(String primaryStorageUuid, String resourceUuid,
                             String resourceType, String hostUuid, long size, Timestamp now);
 
+    /**
+     * 快照元数据持久化完成后的回调。
+     */
     void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid,
                               String resourceType, String hostUuid, long size, Timestamp now);
 
+    /**
+     * 注册回滚后的清理回调,默认无操作。
+     */
     default void afterRegistrationRollback(List<String> resourceUuids) {}
 }

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 7 - 15, 为接口 VmMetadataResourcePersistExtensionPoint 的每个方法补充规范
Javadoc,分别说明方法职责与调用时机以及各参数语义和返回值(getPrimaryStorageType、afterVolumePersist、afterSnapshotPersist、afterRegistrationRollback),并确保不添加不必要的修饰符;例如在
getPrimaryStorageType 说明返回值含义和格式,在 afterVolumePersist/afterSnapshotPersist
说明何时被调用、primaryStorageUuid/resourceUuid/resourceType/hostUuid 的含义、size 单位和 now
的时间语义,在 afterRegistrationRollback 说明何种情况会回滚及 resourceUuids 含义。确保注释简洁明确以作为实现约定。
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)

8-10: 建议补充路径替换扩展点方法的接口文档。

该接口是跨存储插件契约,建议明确输入/输出约束,避免实现方语义不一致。

♻️ 建议修改
 public interface VmMetadataPathReplacementExtensionPoint {
+    /**
+     * `@return` 支持该扩展的主存储类型
+     */
     String getPrimaryStorageType();
+
+    /**
+     * 计算元数据路径替换结果。
+     * `@param` targetPsUuid 目标主存储 UUID
+     * `@param` allOldPaths  旧路径集合
+     * `@return` 路径替换结果
+     */
     PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);

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/VmMetadataPathReplacementExtensionPoint.java`
around lines 8 - 10, 为 VmMetadataPathReplacementExtensionPoint、方法
getPrimaryStorageType()、calculatePathReplacements(String targetPsUuid,
List<String> allOldPaths) 和内部类 PathReplacementResult 添加完整的
Javadoc,明确每个方法的输入/输出约束(例如 targetPsUuid 非空性、allOldPaths
元素含义与允许的空/空列表语义)、返回值含义(PathReplacementResult 中各字段的语义、是否可为
null,以及是否表示“无替换”情形)、并指明线程/重入或异常行为;保持接口方法无多余修饰符并在文档中示例典型用例和失败场景以确保跨存储实现语义一致。
testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy (1)

604-622: 建议让元数据相关模拟器具备最小状态一致性。

当前 5 个新 endpoint 仅返回空响应,未消费请求参数,也未维护写/读/扫/清理之间的状态,测试很难覆盖真实链路。建议至少维护一个内存 map,并在 PREFIX_REBASE_BACKING_FILES_PATH 回填 rebasedCount

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

In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy` around
lines 604 - 622, The five metadata simulators
(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH,
SCAN_VM_METADATA_PATH, CLEANUP_VM_METADATA_PATH and
PREFIX_REBASE_BACKING_FILES_PATH) currently return empty responses and do not
share state; add a simple in-memory map (e.g. Map<String, Map<String,Object>>
metadataStore) keyed by vmInstanceUuid to persist writes, have
WRITE_VM_METADATA_PATH write incoming metadata into the map,
GET_VM_INSTANCE_METADATA_PATH read from it, SCAN_VM_METADATA_PATH
iterate/collect matching entries, CLEANUP_VM_METADATA_PATH remove entries, and
make PREFIX_REBASE_BACKING_FILES_PATH compute and return a populated
PrefixRebaseBackingFilesRsp.rebasedCount based on how many backing files in the
map were rebased.
header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java (1)

5-22: 建议将 vo 重命名为更具语义的字段名。

vo 语义过于宽泛,后续维护时不利于快速理解数据内容。建议改为能体现载荷含义的名称(例如 resourceVoContent / resourceVoJson 等),并同步更新 getter/setter。

As per coding guidelines, “命名缩写:不允许使用不必要的缩写…应使用完整单词提升可读性。”

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

In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`
around lines 5 - 22, Rename the ambiguous field vo in class ResourceMetadata to
a descriptive name (e.g., resourceVoContent or resourceVoJson) and update its
getter and setter methods (getVo/setVo →
getResourceVoContent/setResourceVoContent) accordingly; change the private field
declaration (vo) and the accessor method names and signatures, then update all
usages/references across the codebase to the new names (including any
serialization, JSON property annotations, or reflection-based access) to ensure
compatibility.
header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy (1)

7-66: 建议补充字段说明,避免空 desc

当前多个字段描述为空,会降低 API 文档可读性。建议至少为关键字段(如 schemaVersionmetadataPathincomplete)补充简短语义说明。

🤖 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, Several fields in VmMetadataScanEntryDoc_zh_cn.groovy
(notably vmUuid, vmName, vmCategory, architecture, schemaVersion, metadataPath,
hostUuid, sizeBytes, lastUpdateTime, incomplete) have empty desc values; update
the field blocks for these names to include concise Chinese descriptions (e.g.,
schemaVersion → “元数据模式版本号”, metadataPath → “元数据在主存储上的路径”, incomplete →
“指示元数据是否不完整(true/false)”, vmUuid → “虚拟机唯一标识符”) so API docs are readable and
meaningful while keeping types and since unchanged.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java (1)

3-11: 给扩展接口补上 Javadoc 契约。

这是公开的 extension point,但 buildVmMetadataPathbuildMetadataDirvalidateMetadataPathrequireHostForCleanup 的语义/返回约定现在只能从实现里反推。建议直接在接口上写清楚 path 格式、validateMetadataPathnull/非 null 约定,以及 cleanup 对 hostUuid 的要求,避免不同主存实现各自解释。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, Add Javadoc to the VmMetadataPathBuildExtensionPoint
interface and each method (getPrimaryStorageType, buildVmMetadataPath,
buildMetadataDir, validateMetadataPath, requireHostForCleanup) that clearly
states the contract: describe the expected path formats returned by
buildVmMetadataPath and buildMetadataDir (examples/prefixes, whether paths are
absolute or relative), specify validateMetadataPath return convention (null
means valid, non-null returns a descriptive error message), document when and
why requireHostForCleanup returns true (i.e., cleanup requires a hostUuid) and
what callers should provide, and ensure no unnecessary modifiers are added to
interface methods per coding guidelines.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java (1)

5-5: 移除未使用的导入。

VmInstanceVO 导入未被使用,外键引用的是 VmInstanceEO

♻️ 建议的修改
-import org.zstack.header.vm.VmInstanceVO;
🤖 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/VmMetadataDirtyVO.java` at
line 5, 当前 VmMetadataDirtyVO 中引入了未使用的类 VmInstanceVO;请删除该 import(import
org.zstack.header.vm.VmInstanceVO;),并确认类中引用的外键使用的是 VmInstanceEO(若未导入则添加 import
org.zstack.header.vm.VmInstanceEO;);确保移除后无编译错误并且所有对 VmInstance 实体的引用均指向
VmInstanceEO。
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)

1985-2012: 建议提取重复的 host 选择逻辑

GetVmInstanceMetadataFromPrimaryStorageMsgCleanupVmInstanceMetadataOnPrimaryStorageMsg 的 handler 中有几乎相同的 host 选择逻辑(第 1994-1998 行和第 2048-2052 行)。可以考虑提取为私有辅助方法以减少重复代码。

♻️ 建议的重构
private String selectHostUuid(List<HostInventory> connectedHosts, String preferredHostUuid) {
    if (preferredHostUuid == null || 
        !connectedHosts.stream().anyMatch(h -> h.getUuid().equals(preferredHostUuid))) {
        return connectedHosts.get(0).getUuid();
    }
    return preferredHostUuid;
}

然后在两个 handler 中可以简化为:

String hostUuid = selectHostUuid(connectedHosts, msg.getHostUuid());

Also applies to: 2039-2066

🤖 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 1985 - 2012, Extract the repeated host-selection logic into a
private helper method (e.g., selectHostUuid) and use it in both
handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) and
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg); the helper should
accept List<HostInventory> connectedHosts and a preferred hostUuid and return
either the preferred hostUuid if it exists in connectedHosts or
connectedHosts.get(0).getUuid() otherwise, then replace the existing inline
selection (the code around getHostUuid()/connectedHosts.stream().anyMatch(...))
with a call like String hostUuid = selectHostUuid(connectedHosts,
msg.getHostUuid()) before calling getBackendByHostUuid(...) and
backend.handle(...).
sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java (1)

46-50: 建议为 List 类型添加泛型参数

systemTagsuserTags 字段使用了原始类型 java.util.List,缺少泛型参数。虽然这可能是代码生成的结果,但使用泛型可以提供更好的类型安全性。

♻️ 建议的改进
 `@Param`(required = false)
-public java.util.List systemTags;
+public java.util.List<String> systemTags;

 `@Param`(required = false)
-public java.util.List userTags;
+public java.util.List<String> userTags;
🤖 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/RegisterVmInstanceFromMetadataAction.java`
around lines 46 - 50, The fields systemTags and userTags in
RegisterVmInstanceFromMetadataAction use raw type java.util.List which loses
type safety; change both declarations to a parameterized type (e.g.,
java.util.List<String> if tags are strings) and update any related
getters/setters, constructors, or usages to the same generic type to ensure
compile-time type safety; ensure imports remain correct and adjust any code that
assumed raw List (casts or iterations) to use the typed element type.
sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java (1)

28-35: 建议使用泛型类型以提高类型安全性

vmUuidssystemTagsuserTags 字段使用了原始 List 类型,缺乏泛型约束。建议使用 List<String> 来提高类型安全性。

不过,如果此类是从 API 定义自动生成的,则可以忽略此建议。

♻️ 建议的修改
-    `@Param`(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
-    public java.util.List vmUuids;
+    `@Param`(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
+    public java.util.List<String> vmUuids;

-    `@Param`(required = false)
-    public java.util.List systemTags;
+    `@Param`(required = false)
+    public java.util.List<String> systemTags;

-    `@Param`(required = false)
-    public java.util.List userTags;
+    `@Param`(required = false)
+    public java.util.List<String> userTags;
🤖 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 - 35, Change the raw List fields in CleanupVmInstanceMetadataAction to
use generics: replace vmUuids, systemTags, and userTags declarations with
java.util.List<String> types (keep their existing `@Param` annotations and other
modifiers), and add or ensure the appropriate java.util.List import if needed;
if this class is auto-generated from API definitions, you may ignore this
change.
🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 58-59: 在 CleanupVmInstanceMetadataOnPrimaryStorageGC 中,当从
pluginRgty 获取 VmMetadataPathBuildExtensionPoint(变量名 ext)为 null 时应立即取消本次
GC,不要继续下发 cleanup 消息;也就是在使用 ext 或基于 requireHost 的逻辑之前加入 ext == null
的分支(直接结束/标记为无需处理并返回成功),确保对不支持 metadata 的主存储不再重复触发失败/重试路径(定位到
VmMetadataPathBuildExtensionPoint ext 及 boolean requireHost = ext != null &&
ext.requireHostForCleanup() 所在处修复)。
- Around line 61-72: The code currently cancels the GC when hostUuid is null for
PS types that require a host, which blocks backend fallback logic; update
CleanupVmInstanceMetadataOnPrimaryStorageGC so that when requireHost is true but
hostUuid is null you do NOT call completion.cancel() or return — instead keep
effectiveHostUuid as null (or leave it unset) and only log the debug message
about missing host (using primaryStorageUuid, psType, vmUuid) so the message can
proceed to the backend which can resolve the host from rootVolumeUuid (do this
by removing the completion.cancel() and return in the branch that checks
effectiveHostUuid == null and leaving effectiveHostUuid null for downstream
handling).

In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Around line 1333-1392: The validate(APIRegisterVmInstanceFromMetadataMsg msg)
method misses checking that the cluster belongs to the supplied zoneUuid; add an
explicit check that the ClusterVO with uuid msg.getClusterUuid() exists and
ClusterVO_.zoneUuid equals msg.getZoneUuid(), throwing
ApiMessageInterceptionException(argerr(...)) if not. Reuse that cluster-zone
result in the host validation branch (the HostVO queries that currently only
check clusterUuid should remain but rely on the confirmed cluster-zone binding),
and ensure the error messages reference zoneUuid/clusterUuid consistency when
failing.

In `@header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java`:
- Line 16: APIDeleteTagMsg is annotated to use SystemTagUuidToVmUuidResolver but
its API path allows both SystemTagVO and UserTagVO; either make the resolver
generic or restrict the API. Fix by either (A) replacing
SystemTagUuidToVmUuidResolver with a generic resolver such as
TagUuidToVmUuidResolver in APIDeleteTagMsg so UUIDs for both SystemTagVO and
UserTagVO resolve correctly, or (B) restrict APIDeleteTagMsg to system-only tags
by adding `@APIParam`(resourceType = SystemTagVO.class) on the tag parameter to
match SystemTagUuidToVmUuidResolver (consistent with APIUpdateSystemTagMsg).
Ensure the chosen change updates the resolver reference or adds the APIParam
annotation on APIDeleteTagMsg accordingly.

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Line 7: Add Javadoc comments to the interface methods resolveVmUuid(String
fieldValue) and batchResolveVmUuids(List<String> fieldValues) in
VmUuidFromApiResolver to specify parameter semantics, exact return values and
types, null/empty input behavior, and exception guarantees; explicitly document
whether methods may return null or an empty collection, whether they throw on
unresolved values or return partial results, and any thread-safety or
performance expectations so callers know the contract. Ensure the Javadoc
includes `@param` and `@return` tags (and `@throws` if applicable) and remove any
unnecessary modifiers per interface style guidelines (do not add public/static)
while keeping method names resolveVmUuid and batchResolveVmUuids unchanged.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3503-3551: The log computes successHosts incorrectly by
subtracting failedHosts.size() from connectedHostUuids.size() while failedHosts
already includes disconnectedHostUuids; change the calculation to compute
totalHosts = connectedHostUuids.size() + disconnectedHostUuids.size() (or use
connectedHostUuids.size() + disconnectedHostUuids.size() inline) and set
successHosts = totalHosts - failedHosts.size() when building the warning log
around failedHosts/ successHosts in LocalStorageBase (reference symbols:
failedHosts, connectedHostUuids, disconnectedHostUuids, the While completion
block that sets reply and logs the partial scan failure).

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 375-382: In LocalStorageSimulator, the prefixRebaseBackingFiles
handler currently does not persist the incoming PrefixRebaseBackingFilesCmd to
the simulator config; modify prefixRebaseBackingFiles(HttpEntity<String> entity)
to deserialize the cmd (PrefixRebaseBackingFilesCmd) and store it into the
simulator's config/state the same way other metadata endpoints do (so tests can
assert the call and inspect inputs), then continue to build
PrefixRebaseBackingFilesRsp and reply; ensure you use the same simulator config
key/structure and naming convention used by other handlers in
LocalStorageSimulator for consistency.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 151-168: The createResourceRef method currently checks existence
only by resourceUuid but LocalStorageResourceRefVO uses a composite key of
(resourceUuid, primaryStorageUuid, hostUuid), so update the existence check to
query by all three fields (resourceUuid, primaryStorageUuid, hostUuid) before
deciding to skip creation, and wrap the dbf.persist(ref) call in a try/catch to
handle unique-constraint/duplicate-key exceptions (catch the DB-specific runtime
exception, log a debug/info message including the three-key values and swallow
or return gracefully); reference the createResourceRef method,
LocalStorageResourceRefVO fields resourceUuid/primaryStorageUuid/hostUuid, and
the dbf.persist call and logger for locating where to change logic.

---

Nitpick comments:
In
`@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java`:
- Around line 6-14: The field and accessors in ReadVmInstanceMetadataReply
(vmMetadata, getVmMetadata, setVmMetadata) use a different name than other
replies that use metadata; rename the field and its getters/setters to metadata
(or vice versa to match the majority) so the DTO/serialization mapping is
consistent, update all usages and imports that reference
vmMetadata/getVmMetadata/setVmMetadata, and add/adjust any
JSON/XML/serialization annotations (e.g., `@JsonProperty`) on the field or methods
if renaming would break external contracts to preserve wire compatibility.

In
`@header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy`:
- Around line 7-66: Several fields in VmMetadataScanEntryDoc_zh_cn.groovy
(notably vmUuid, vmName, vmCategory, architecture, schemaVersion, metadataPath,
hostUuid, sizeBytes, lastUpdateTime, incomplete) have empty desc values; update
the field blocks for these names to include concise Chinese descriptions (e.g.,
schemaVersion → “元数据模式版本号”, metadataPath → “元数据在主存储上的路径”, incomplete →
“指示元数据是否不完整(true/false)”, vmUuid → “虚拟机唯一标识符”) so API docs are readable and
meaningful while keeping types and since unchanged.

In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`:
- Around line 5-22: Rename the ambiguous field vo in class ResourceMetadata to a
descriptive name (e.g., resourceVoContent or resourceVoJson) and update its
getter and setter methods (getVo/setVo →
getResourceVoContent/setResourceVoContent) accordingly; change the private field
declaration (vo) and the accessor method names and signatures, then update all
usages/references across the codebase to the new names (including any
serialization, JSON property annotations, or reflection-based access) to ensure
compatibility.

In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Line 5: 当前 VmMetadataDirtyVO 中引入了未使用的类 VmInstanceVO;请删除该 import(import
org.zstack.header.vm.VmInstanceVO;),并确认类中引用的外键使用的是 VmInstanceEO(若未导入则添加 import
org.zstack.header.vm.VmInstanceEO;);确保移除后无编译错误并且所有对 VmInstance 实体的引用均指向
VmInstanceEO。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java`:
- Around line 3-11: Add Javadoc to the VmMetadataPathBuildExtensionPoint
interface and each method (getPrimaryStorageType, buildVmMetadataPath,
buildMetadataDir, validateMetadataPath, requireHostForCleanup) that clearly
states the contract: describe the expected path formats returned by
buildVmMetadataPath and buildMetadataDir (examples/prefixes, whether paths are
absolute or relative), specify validateMetadataPath return convention (null
means valid, non-null returns a descriptive error message), document when and
why requireHostForCleanup returns true (i.e., cleanup requires a hostUuid) and
what callers should provide, and ensure no unnecessary modifiers are added to
interface methods per coding guidelines.

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 8-10: 为 VmMetadataPathReplacementExtensionPoint、方法
getPrimaryStorageType()、calculatePathReplacements(String targetPsUuid,
List<String> allOldPaths) 和内部类 PathReplacementResult 添加完整的
Javadoc,明确每个方法的输入/输出约束(例如 targetPsUuid 非空性、allOldPaths
元素含义与允许的空/空列表语义)、返回值含义(PathReplacementResult 中各字段的语义、是否可为
null,以及是否表示“无替换”情形)、并指明线程/重入或异常行为;保持接口方法无多余修饰符并在文档中示例典型用例和失败场景以确保跨存储实现语义一致。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java`:
- Around line 7-15: 为接口 VmMetadataResourcePersistExtensionPoint 的每个方法补充规范
Javadoc,分别说明方法职责与调用时机以及各参数语义和返回值(getPrimaryStorageType、afterVolumePersist、afterSnapshotPersist、afterRegistrationRollback),并确保不添加不必要的修饰符;例如在
getPrimaryStorageType 说明返回值含义和格式,在 afterVolumePersist/afterSnapshotPersist
说明何时被调用、primaryStorageUuid/resourceUuid/resourceType/hostUuid 的含义、size 单位和 now
的时间语义,在 afterRegistrationRollback 说明何种情况会回滚及 resourceUuids 含义。确保注释简洁明确以作为实现约定。

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1985-2012: Extract the repeated host-selection logic into a
private helper method (e.g., selectHostUuid) and use it in both
handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) and
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg); the helper should
accept List<HostInventory> connectedHosts and a preferred hostUuid and return
either the preferred hostUuid if it exists in connectedHosts or
connectedHosts.get(0).getUuid() otherwise, then replace the existing inline
selection (the code around getHostUuid()/connectedHosts.stream().anyMatch(...))
with a call like String hostUuid = selectHostUuid(connectedHosts,
msg.getHostUuid()) before calling getBackendByHostUuid(...) and
backend.handle(...).

In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java`:
- Around line 28-35: Change the raw List fields in
CleanupVmInstanceMetadataAction to use generics: replace vmUuids, systemTags,
and userTags declarations with java.util.List<String> types (keep their existing
`@Param` annotations and other modifiers), and add or ensure the appropriate
java.util.List import if needed; if this class is auto-generated from API
definitions, you may ignore this change.

In `@sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java`:
- Around line 46-50: The fields systemTags and userTags in
RegisterVmInstanceFromMetadataAction use raw type java.util.List which loses
type safety; change both declarations to a parameterized type (e.g.,
java.util.List<String> if tags are strings) and update any related
getters/setters, constructors, or usages to the same generic type to ensure
compile-time type safety; ensure imports remain correct and adjust any code that
assumed raw List (casts or iterations) to use the typed element type.

In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-622: The five metadata simulators
(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH,
SCAN_VM_METADATA_PATH, CLEANUP_VM_METADATA_PATH and
PREFIX_REBASE_BACKING_FILES_PATH) currently return empty responses and do not
share state; add a simple in-memory map (e.g. Map<String, Map<String,Object>>
metadataStore) keyed by vmInstanceUuid to persist writes, have
WRITE_VM_METADATA_PATH write incoming metadata into the map,
GET_VM_INSTANCE_METADATA_PATH read from it, SCAN_VM_METADATA_PATH
iterate/collect matching entries, CLEANUP_VM_METADATA_PATH remove entries, and
make PREFIX_REBASE_BACKING_FILES_PATH compute and return a populated
PrefixRebaseBackingFilesRsp.rebasedCount based on how many backing files in the
map were rebased.
🪄 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: 8f42f398-ed9b-4c87-b730-da319eebc37d

📥 Commits

Reviewing files that changed from the base of the PR and between 0b0c1ad and 0f8f1d4.

⛔ 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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_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 thread header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
import java.util.List;

public interface VmUuidFromApiResolver {
String resolveVmUuid(String fieldValue);
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,明确契约语义。

resolveVmUuidbatchResolveVmUuids 当前缺少方法级说明,建议补充参数、返回值及 null 处理约定,避免调用方误用。

建议补丁
 public interface VmUuidFromApiResolver {
+    /**
+     * 将 API 字段值解析为 VM UUID。
+     *
+     * `@param` fieldValue API 请求中的字段值
+     * `@return` 解析后的 VM UUID;无法解析时返回 null
+     */
     String resolveVmUuid(String fieldValue);
 
+    /**
+     * 批量解析 VM UUID。
+     * <p>
+     * 会忽略 null 输入项和解析结果为 null 的项,返回仅包含有效 VM UUID 的列表。
+     *
+     * `@param` fieldValues API 请求中的字段值集合
+     * `@return` 有效 VM UUID 列表;当输入为 null 或空时返回空列表
+     */
     default List<String> batchResolveVmUuids(List<String> fieldValues) {
         List<String> result = new ArrayList<>();
         if (fieldValues == null || fieldValues.isEmpty()) {
             return result;
         }

As per coding guidelines, 接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。

Also applies to: 9-24

🤖 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`
at line 7, Add Javadoc comments to the interface methods resolveVmUuid(String
fieldValue) and batchResolveVmUuids(List<String> fieldValues) in
VmUuidFromApiResolver to specify parameter semantics, exact return values and
types, null/empty input behavior, and exception guarantees; explicitly document
whether methods may return null or an empty collection, whether they throw on
unresolved values or return partial results, and any thread-safety or
performance expectations so callers know the contract. Ensure the Javadoc
includes `@param` and `@return` tags (and `@throws` if applicable) and remove any
unnecessary modifiers per interface style guidelines (do not add public/static)
while keeping method names resolveVmUuid and batchResolveVmUuids unchanged.

Comment on lines +375 to +382
@RequestMapping(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, method= RequestMethod.POST)
public @ResponseBody
String prefixRebaseBackingFiles(HttpEntity<String> entity) {
PrefixRebaseBackingFilesCmd cmd = JSONObjectUtil.toObject(entity.getBody(), PrefixRebaseBackingFilesCmd.class);
PrefixRebaseBackingFilesRsp rsp = new PrefixRebaseBackingFilesRsp();
rsp.rebasedCount = cmd.filePaths == null ? 0 : cmd.filePaths.size();
reply(entity, rsp);
return null;
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

把 PrefixRebase 请求也记录到 simulator config。

这个分支是新增 endpoint 里唯一没有保存 cmd 的实现。这样测试侧就没法断言该 HTTP 调用是否真的发生,也拿不到入参内容,和前面几个 metadata 接口的 simulator 行为不一致。

🤖 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/LocalStorageSimulator.java`
around lines 375 - 382, In LocalStorageSimulator, the prefixRebaseBackingFiles
handler currently does not persist the incoming PrefixRebaseBackingFilesCmd to
the simulator config; modify prefixRebaseBackingFiles(HttpEntity<String> entity)
to deserialize the cmd (PrefixRebaseBackingFilesCmd) and store it into the
simulator's config/state the same way other metadata endpoints do (so tests can
assert the call and inspect inputs), then continue to build
PrefixRebaseBackingFilesRsp and reply; ensure you use the same simulator config
key/structure and naming convention used by other handlers in
LocalStorageSimulator for consistency.

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

@coderabbitai coderabbitai bot Apr 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 搜索调用 afterVolumePersist 和 afterSnapshotPersist 的位置,检查是否有同步保护
rg -n -A5 'afterVolumePersist|afterSnapshotPersist' --type=java

Repository: MatheMatrix/zstack

Length of output: 3246


🏁 Script executed:

rg -n 'VmMetadataResourcePersistExtensionPoint|afterVolumePersist|afterSnapshotPersist' --type=java -B3 -A3 | head -100

Repository: MatheMatrix/zstack

Length of output: 6160


🏁 Script executed:

# Find the implementation class that calls these extension points
rg -n 'getExtensionsByInterfaceNames|notifyExtensionList.*VmMetadataResourcePersistExtensionPoint' --type=java -B2 -A5

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check the schema for LocalStorageResourceRefVO to see if there are unique constraints
find . -name '*LocalStorageResourceRefVO*' -o -name '*LocalStorageResourceRef*' | grep -E '\.(java|xml)$'

Repository: MatheMatrix/zstack

Length of output: 934


🏁 Script executed:

cat plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageResourceRefVO.java

Repository: MatheMatrix/zstack

Length of output: 2732


🏁 Script executed:

# Search for where the extension point methods are called/invoked
rg -n 'afterVolumePersist|afterSnapshotPersist' --type=java -B5 -A2 | head -150

Repository: MatheMatrix/zstack

Length of output: 3856


🏁 Script executed:

# Find implementations that call these extension point methods
rg -n 'getExtensionByInterfaceName|for.*VmMetadataResourcePersistExtensionPoint' --type=java -B3 -A10

Repository: MatheMatrix/zstack

Length of output: 8627


🏁 Script executed:

rg -n 'VmMetadataResourcePersistExtensionPoint|afterVolumePersist|afterSnapshotPersist' --type=java -B10 -A10 | grep -E '(afterVolumePersist|afterSnapshotPersist|notifyExtension|for.*extension)' | head -50

Repository: MatheMatrix/zstack

Length of output: 796


🏁 Script executed:

# Search for who calls the extension points - look for notify/invoke patterns
rg -n 'notifyExtension|invokeExtension|for.*VmMetadataResourcePersistExtensionPoint' --type=java -B5 -A10

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Check if CompositePrimaryKeyForLocalStorageResourceRefVO reveals the composite key constraint
cat plugin/localstorage/src/main/java/org/zstack/storage/primary/local/CompositePrimaryKeyForLocalStorageResourceRefVO.java

Repository: MatheMatrix/zstack

Length of output: 2084


🏁 Script executed:

# Verify the exact check-then-act pattern and look for exception handling
sed -n '151,168p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java

Repository: MatheMatrix/zstack

Length of output: 940


🏁 Script executed:

# Check if there's any try-catch or exception handling around dbf.persist() calls
rg -n 'dbf\.persist|catch|throws' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java -B2 -A2

Repository: MatheMatrix/zstack

Length of output: 477


🏁 Script executed:

# Look for similar patterns in the codebase to understand ZStack convention
rg -n 'isExists().*resourceUuid.*primaryStorage' --type=java -A5 -B2 | head -60

Repository: MatheMatrix/zstack

Length of output: 44


存在性检查与复合主键不匹配,缺少异常处理

createResourceRef 方法的存在性检查仅验证 resourceUuid 一个字段(Line 153),但 LocalStorageResourceRefVO 的复合主键由 (resourceUuid, primaryStorageUuid, hostUuid) 三个字段组成。这导致检查逻辑不完整:

  1. 逻辑缺陷:若相同 resourceUuid 的记录已存在但对应的 primaryStorageUuidhostUuid 不同,检查会错误地跳过创建,即使该新组合应该被允许。
  2. 缺少异常处理:若两个并发调用传入完全相同的三元组,均会通过检查并尝试执行 dbf.persist(ref)(Line 167),数据库约束冲突会导致异常抛出而不被捕获。

建议改用完整的复合键检查,或添加异常处理来优雅地处理约束冲突(如捕获重复键异常并记录)。

🤖 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 currently checks existence
only by resourceUuid but LocalStorageResourceRefVO uses a composite key of
(resourceUuid, primaryStorageUuid, hostUuid), so update the existence check to
query by all three fields (resourceUuid, primaryStorageUuid, hostUuid) before
deciding to skip creation, and wrap the dbf.persist(ref) call in a try/catch to
handle unique-constraint/duplicate-key exceptions (catch the DB-specific runtime
exception, log a debug/info message including the three-key values and swallow
or return gracefully); reference the createResourceRef method,
LocalStorageResourceRefVO fields resourceUuid/primaryStorageUuid/hostUuid, and
the dbf.persist call and logger for locating where to change logic.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Comment from tao.gan:

image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

I'm unable to view the image in the comment. Let me check the actual key structure of LocalStorageResourceRefVO to understand what tao.gan likely pointed out.


🏁 Script executed:

#!/bin/bash
cat plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageResourceRefVO.java
echo "---"
cat plugin/localstorage/src/main/java/org/zstack/storage/primary/local/CompositePrimaryKeyForLocalStorageResourceRefVO.java

Length of output: 4697


@MatheMatrix, I'm unable to view the image that tao.gan attached. However, after inspecting the actual code, I can confirm that the original concern remains valid:

LocalStorageResourceRefVO is explicitly annotated with @IdClass(CompositePrimaryKeyForLocalStorageResourceRefVO.class), and all three fields — resourceUuid, primaryStorageUuid, and hostUuid — are each annotated with @Id, forming a true composite primary key. CompositePrimaryKeyForLocalStorageResourceRefVO also implements equals/hashCode using all three fields.

The current existence check:

boolean exists = Q.New(LocalStorageResourceRefVO.class)
    .eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid)
    .isExists();

only filters by resourceUuid. A correct check should include all three key parts:

boolean exists = Q.New(LocalStorageResourceRefVO.class)
    .eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid)
    .eq(LocalStorageResourceRefVO_.primaryStorageUuid, primaryStorageUuid)
    .eq(LocalStorageResourceRefVO_.hostUuid, hostUuid)
    .isExists();

Could you share what tao.gan's image was explaining? If there is a business reason why resourceUuid is always unique in practice for this specific call site (e.g., a volume/snapshot UUID is always bound to exactly one host and one primary storage at persist time), that would be worth recording as a learning. Otherwise, the composite-key check fix and adding exception handling around dbf.persist() for concurrent duplicates still seem warranted.


🧠 Learnings used
Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 3737
File: compute/src/main/java/org/zstack/compute/vm/devices/VmHostFileCascadeExtension.java:77-113
Timestamp: 2026-04-14T05:45:06.368Z
Learning: In ZStack's VmHostFileCascadeExtension (compute/src/main/java/org/zstack/compute/vm/devices/VmHostFileCascadeExtension.java), deleting ALL VmHostFileVO records for a VM during cascade deletion is correct behavior. VmHostFileVO.vmInstanceUuid has an ON DELETE CASCADE foreign key to VmInstanceEO, so VmHostFileVO records cannot exist independently of the VM's EO record. The 90-day "latest file" retention policy in VmHostFileTracker applies only while VmInstanceEO is in the soft-deleted state — there is no way to preserve a VmHostFileVO record beyond the VM's EO lifecycle. Suggesting to skip the latest record in the cascade extension is incorrect.

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 3665
File: plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java:341-347
Timestamp: 2026-04-02T07:45:31.705Z
Learning: In ZStack's VmHostFileTracker (plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java), TPM and NvRam host files only require the single latest copy to be retained. For deleted VMs, non-latest VmHostFileVO records have no practical value and can be deleted immediately upon a successful KVM delete command, regardless of their age (no 7-day retention grace period is needed for non-latest files). Only the latest file needs to be retained for up to 90 days.

Learnt from: zstack-robot-2
Repo: MatheMatrix/zstack PR: 2435
File: storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java:47-47
Timestamp: 2025-08-14T06:56:19.585Z
Learning: 在VolumeSnapshotGroupBase.java中,VmInstanceResourceMetadataManager的注入和SKIP_RESOURCE_ROLLBACK标记虽然在当前版本中未被使用,但这些导入在大型重构PR中是有意为之的,用于保持代码一致性或为后续功能做准备。

Learnt from: MatheMatrix
Repo: MatheMatrix/zstack PR: 2504
File: plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java:1010-1010
Timestamp: 2025-08-25T03:53:30.749Z
Learning: The OFFLINE_COMMIT_PATH ("/localstorage/snapshot/offlinecommit") for local storage snapshot commit operations is properly handled on both the ZStack management node side (in LocalStorageKvmBackend.java) and the KVM Agent side, providing complete end-to-end offline snapshot commit functionality.

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 0
File: :0-0
Timestamp: 2026-04-02T01:40:40.916Z
Learning: 在 ZStack 中,资源创建成功路径(如 `handle(APIAddSdnControllerMsg)` 中)对 `tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), SdnControllerVO.class.getSimpleName())` 和 `dbf.findByUuid(vo.getUuid(), SdnControllerVO.class)` 等后续操作,不需要用 try/catch 来防御"资源不存在"异常。ZStack 的设计哲学是:在资源创建流程中,通过生命周期管理和数据库外键约束来保证一致性,一般假设此类情况不会发生;若需要解决框架层面的一致性问题,应通过声明式 API 等机制统一处理,而不是在每个操作中无休止地判断异常。(plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java)

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2504
File: storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotTreeBase.java:775-819
Timestamp: 2025-08-25T03:52:37.301Z
Learning: 在ZStack项目的VolumeSnapshotTreeBase类中,当通过dbf.findByUuid()方法获取VolumeVO对象时,需要进行null检查,因为该方法可能在找不到对应记录时返回null,直接使用可能导致NullPointerException。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 3716
File: header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java:32-35
Timestamp: 2026-04-12T05:42:20.685Z
Learning: In ZStack's KvmTpmExtensions.preReleaseVmResource (plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java), as of PR `#3716`, the `ResourceKeyResult.refExistedBeforeCreate` field has been removed from `EncryptedResourceKeyManager.ResourceKeyResult`. Rollback behavior is now determined solely by `ResourceKeyResult.isCreatedNewKey()`: when true, the implementation deletes the materialized key-tool secret and removes the `EncryptedResourceKeyRef` row; when false, rollback is a no-op. The previously hardcoded `result.setRefExistedBeforeCreate(true)` in `preReleaseVmResource` has also been removed as part of this redesign.

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 3307
File: conf/db/zsv/V5.0.0__schema.sql:38-76
Timestamp: 2026-02-10T08:53:52.891Z
Learning: 在 ZStack 项目中,对于 joined-table inheritance 模式(如 KVMHostVO→HostEO, ApplianceVmVO→VmInstanceEO),子表的 uuid 字段不添加到父表的外键约束。ZStack 依赖 Hibernate/JPA 在应用层管理继承关系和级联操作,外键约束仅在数据库层直接删除时才有效,因此不使用。

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 0
File: :0-0
Timestamp: 2025-08-03T04:10:21.683Z
Learning: ZStack 数据库升级脚本 V4.10.16__schema.sql 中的 UPGRADE_VM_METADATA_TABLES_DIRECT 存储过程使用直接 RENAME TABLE 操作,不具备幂等性。需要通过 information_schema 检查表和约束的存在性来确保脚本可以安全重复执行。

Learnt from: MatheMatrix
Repo: MatheMatrix/zstack PR: 3742
File: plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java:214-233
Timestamp: 2026-04-14T10:03:17.395Z
Learning: In KvmTpmExtensions.preInstantiateVmResource (plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java), when context.providerUuid is blank and tpmSpec.getKeyProviderUuid() is also blank, it is intentional to NOT fail early or attempt by-name rebind at that point. The flow must continue to downstream steps (e.g. get-secret-on-host-first) to first check whether cached data is available before deciding to fail. Early failure here would break the NKP restore scenario.

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 2489
File: storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotTreeBase.java:179-179
Timestamp: 2025-08-22T06:31:57.406Z
Learning: 在ZStack项目的VolumeSnapshotAO类中,团队决定不为treeUuid字段添加Index注解,即使这可能会影响查询性能。团队优先考虑代码稳定性而非性能优化。

Learnt from: MatheMatrix
Repo: MatheMatrix/zstack PR: 2410
File: compute/src/main/java/org/zstack/compute/vm/VmInstanceHelper.java:334-356
Timestamp: 2025-08-10T13:42:01.027Z
Learning: 在 ZStack 的 `VmInstanceHelper.validateVmNicParams` 方法中,当 vmNicParams 非空但 l3NetworkUuids 为空时的报错文案 "l3NetworkUuids and vmNicInventories mustn't both be empty or both be set" 需要保持不变,以维持与现有 i18n 的一致性,即使文案中提到的是 vmNicInventories 而非 vmNicParams。

Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 3782
File: header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java:16-16
Timestamp: 2026-04-17T07:29:41.559Z
Learning: In ZStack, APIDeleteTagMsg (/tags/{uuid}) only handles system tags (SystemTagVO); UserTagVO has been removed/is no longer retained. Therefore, using SystemTagUuidToVmUuidResolver as the MetadataImpact resolver for APIDeleteTagMsg is correct and there is no need to add a generic tag resolver or restrict with APIParam(resourceType = SystemTagVO.class). (header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java)

Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 3563
File: plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java:328-334
Timestamp: 2026-03-23T09:33:01.215Z
Learning: In ZStack message handlers, ensure your `handleMessage` method is annotated with `MessageSafe` (`org.zstack.core.cloudbus.MessageSafe`). This enables `MessageSafeAspect.aj` to automatically convert exceptions thrown by the handler into appropriate reply errors. Note that `ExceptionSafeAspect.aj` only applies to methods annotated with `ExceptionSafe` and will only log/swallow exceptions without sending a reply—so missing `MessageSafe` can break reply/error semantics for the message bus.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java (1)

73-87: ⚠️ Potential issue | 🟠 Major

hostUuid 为空但 rootVolumeUuid 存在时,仍会被提前取消,回退路径被挡住。

在 Line 80,effectiveHostUuid == null 的情况下仍执行 dbf.isExist(effectiveHostUuid, HostVO.class),会导致进入取消分支,消息无法下发到后端通过 rootVolumeUuid 解析 host。该行为与本 PR 的回退目标冲突。

建议修改
         if (!requireHost) {
             effectiveHostUuid = null;
         } else {
             if (effectiveHostUuid == null && rootVolumeUuid == null) {
                 logger.debug(String.format("[MetadataCleanupGC] hostUuid and rootVolumeUuid are both null, " +
                                 "ps[uuid:%s, type:%s] requires host for cleanup, cancel gc for vm[uuid:%s]",
                         primaryStorageUuid, psType, vmUuid));
                 completion.cancel();
                 return;
             }
-            if (!dbf.isExist(effectiveHostUuid, HostVO.class)) {
+            if (effectiveHostUuid != null && !dbf.isExist(effectiveHostUuid, HostVO.class)) {
                 logger.debug(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists " +
                                 "and ps[uuid:%s, type:%s] requires host for cleanup, " +
                                 "metadata is unreachable, cancel gc for vm[uuid:%s]",
                         effectiveHostUuid, primaryStorageUuid, psType, vmUuid));
                 completion.cancel();
                 return;
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`
around lines 73 - 87, The code cancels GC when effectiveHostUuid is null even
though rootVolumeUuid may allow a fallback; update the conditional logic in
CleanupVmInstanceMetadataOnPrimaryStorageGC so that you only call
dbf.isExist(effectiveHostUuid, HostVO.class) and potentially cancel via
completion.cancel() when effectiveHostUuid is non-null, and if effectiveHostUuid
is null but rootVolumeUuid is non-null, skip the cancel branch and allow the
fallback path that resolves host from rootVolumeUuid to proceed (adjust the if
checks around effectiveHostUuid, rootVolumeUuid, dbf.isExist, and
completion.cancel accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 73-87: The code cancels GC when effectiveHostUuid is null even
though rootVolumeUuid may allow a fallback; update the conditional logic in
CleanupVmInstanceMetadataOnPrimaryStorageGC so that you only call
dbf.isExist(effectiveHostUuid, HostVO.class) and potentially cancel via
completion.cancel() when effectiveHostUuid is non-null, and if effectiveHostUuid
is null but rootVolumeUuid is non-null, skip the cancel branch and allow the
fallback path that resolves host from rootVolumeUuid to proceed (adjust the if
checks around effectiveHostUuid, rootVolumeUuid, dbf.isExist, and
completion.cancel accordingly).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 86a9bf7b-2f58-4706-a866-39f06240dacc

📥 Commits

Reviewing files that changed from the base of the PR and between 0f8f1d4 and 09a8976.

📒 Files selected for processing (3)
  • compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
✅ Files skipped from review due to trivial changes (1)
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java

@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/vm_metadata-ZSV-11559@@3 branch from 09a8976 to bf1c5a6 Compare April 17, 2026 07:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 80-87: The host-existence check in
CleanupVmInstanceMetadataOnPrimaryStorageGC incorrectly calls
dbf.isExist(effectiveHostUuid, HostVO.class) when effectiveHostUuid is null but
rootVolumeUuid is present, which can falsely cancel GC; change the logic so that
if requireHost is true and effectiveHostUuid is null but rootVolumeUuid != null
you first resolve the host UUID from rootVolumeUuid (or derive effectiveHostUuid
from the root volume) before calling dbf.isExist, and only call
completion.cancel() when no host can be resolved; update the condition around
effectiveHostUuid, rootVolumeUuid, dbf.isExist(...) and completion.cancel()
accordingly to avoid cancelling when a host can be inferred from rootVolumeUuid.
🪄 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: 7acd22e9-ec40-4f5d-9d35-bab99f132a5f

📥 Commits

Reviewing files that changed from the base of the PR and between 09a8976 and bf1c5a6.

📒 Files selected for processing (3)
  • compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java

Comment on lines +80 to +87
if (!dbf.isExist(effectiveHostUuid, HostVO.class)) {
logger.debug(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists " +
"and ps[uuid:%s, type:%s] requires host for cleanup, " +
"metadata is unreachable, cancel gc for vm[uuid:%s]",
effectiveHostUuid, primaryStorageUuid, psType, vmUuid));
completion.cancel();
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

effectiveHostUuid 为 null 但 rootVolumeUuid 不为 null 时,主机存在性检查逻辑有误。

根据 Lines 73-79 的逻辑,当 requireHost 为 true 且 effectiveHostUuid == nullrootVolumeUuid != null 时,代码会继续执行到 Line 80。此时 dbf.isExist(null, HostVO.class) 会被调用,可能返回 false 导致 GC 被错误取消,而实际上后端可以通过 rootVolumeUuid 推导 host。

建议修改
-        if (!dbf.isExist(effectiveHostUuid, HostVO.class)) {
+        if (effectiveHostUuid != null && !dbf.isExist(effectiveHostUuid, HostVO.class)) {
             logger.debug(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists " +
                             "and ps[uuid:%s, type:%s] requires host for cleanup, " +
                             "metadata is unreachable, cancel gc for vm[uuid:%s]",
                     effectiveHostUuid, primaryStorageUuid, psType, vmUuid));
             completion.cancel();
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`
around lines 80 - 87, The host-existence check in
CleanupVmInstanceMetadataOnPrimaryStorageGC incorrectly calls
dbf.isExist(effectiveHostUuid, HostVO.class) when effectiveHostUuid is null but
rootVolumeUuid is present, which can falsely cancel GC; change the logic so that
if requireHost is true and effectiveHostUuid is null but rootVolumeUuid != null
you first resolve the host UUID from rootVolumeUuid (or derive effectiveHostUuid
from the root volume) before calling dbf.isExist, and only call
completion.cancel() when no host can be resolved; update the condition around
effectiveHostUuid, rootVolumeUuid, dbf.isExist(...) and completion.cancel()
accordingly to avoid cancelling when a host can be inferred from rootVolumeUuid.

@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/vm_metadata-ZSV-11559@@3 branch 2 times, most recently from 24c1536 to 8ad9325 Compare April 17, 2026 08:43
Resolves: ZSV-11559

Change-Id: I3cd9fa26b6af7bd47787ab88c5050683a3356059
@zstack-robot-1
Copy link
Copy Markdown
Collaborator

Comment from gitlab:

自上次添加REVIEWED标签(2026-04-17 22:02:32.000Z)后, 有新的COMMIT更新(2026-04-18 15:21:36.274Z), 所以移除了REVIEWED标签

@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/vm_metadata-ZSV-11559@@3 branch from 8ad9325 to 22f2626 Compare April 18, 2026 07:22
@zstack-robot-1 zstack-robot-1 deleted the sync/tao.gan/vm_metadata-ZSV-11559@@3 branch April 19, 2026 00:17
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.

5 participants