Skip to content

<fix>[vm]: add MetadataImpact#3603

Closed
zstack-robot-2 wants to merge 1 commit into
feature-zsv-5.0.0-vm-registrationfrom
sync/tao.gan/ZSV-11559@@4
Closed

<fix>[vm]: add MetadataImpact#3603
zstack-robot-2 wants to merge 1 commit into
feature-zsv-5.0.0-vm-registrationfrom
sync/tao.gan/ZSV-11559@@4

Conversation

@zstack-robot-2

Copy link
Copy Markdown
Collaborator

APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373

sync from gitlab !9463

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Walkthrough

新增完整云主机元数据子系统:模型、JPA/DB 表、全局配置、扩展点、消息/API/事件/文档、主存储(Local/NFS)后端与 KVM agent 交互、SDK、测试模拟器、GC 与流程整合(扫描/获取/更新/清理/注册/路径替换/重试)。

Changes

Cohort / File(s) Summary
数据库迁移
conf/db/upgrade/V5.0.0__schema.sql
新增 VmMetadataDirtyVOVmMetadataFingerprintVO 表及关联外键和索引。
全局配置
compute/src/main/java/.../VmGlobalConfig.java
新增 VM 元数据相关 GlobalConfig(开关、版本、flush 并发/轮询/批量、GC 间隔)。
流程与 GC
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java, .../CleanupVmInstanceMetadataOnPrimaryStorageGC.java
新增 VmExpungeMetadataFlow 与 CleanupVmInstanceMetadataOnPrimaryStorageGC:发送主存储清理消息,失败时提交去重 GC 重试。
API 拦截与系统标签
compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java, .../VmSystemTags.java
为注册 API 增加 metadataPath 校验与扩展点验证,新增注册用系统标签常量。
主存储消息/回复/DTO
header/src/main/java/org/zstack/header/storage/primary/*
新增消息/回复类型(Update/Get/Scan/Cleanup/Rebase 等)及 DTO VmMetadataScanEntry
VM 元数据 REST API 与文档
header/src/main/java/org/zstack/header/vm/metadata/API*.java, *Doc_zh_cn.groovy
新增 Register/Cleanup/Update/Get/Scan 等 API 消息、事件与中文文档。
元数据模型、常量与错误码
header/src/main/java/org/zstack/header/vm/metadata/*
新增 DTO(VmInstanceMetadataDTO、ResourceMetadata、VolumeResourceMetadata)、常量类、枚举(VmMetadataCategory)、错误码(VmMetadataErrors)。
JPA VO 与元模型
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO*.java, VmMetadataFingerprintVO.java
新增脏标记与指纹实体、静态元模型类与相应字段与生命周期处理。
扩展点与解析器接口
header/src/main/java/org/zstack/header/vm/metadata/*ExtensionPoint.java, VmUuidFromApiResolver.java
新增路径构建/替换/持久化扩展点以及 API→VM UUID 解析器接口以供存储类型实现。
MetadataImpact 注解与 API 标注
header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java, 多处 header/.../*.java
新增运行时注解 @MetadataImpact(NONE/CONFIG/STORAGE),并为 40+ 个 API 类添加或更新注解以声明元数据影响及 resolver/field。
主存储基类路由
storage/src/main/java/.../PrimaryStorageBase.java
扩展 handleLocalMessage 与 API 路由以处理元数据消息及 APIScan(过滤 TEMPLATE_CACHE)。
Local 存储实现
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/*
增加后端 handler(Update/Get/Scan/Cleanup/Rebase)、agent DTO、LocalStorageVmMetadataExtension(路径构建/验证/替换/持久化)、模拟器端点与配置。
NFS 存储实现
plugin/nfsPrimaryStorage/src/main/java/.../*
增加 KVM agent 命令/响应 DTO、HTTP 路径常量、后端 handler 与 NfsVmMetadataExtension(路径构建/验证/替换),并实现主机代理调用。
后端抽象接口
plugin/localstorage/.../LocalStorageHypervisorBackend.java, plugin/nfsPrimaryStorage/.../NfsPrimaryStorageBackend.java
在 hypervisor/backend 抽象中新增元数据相关 handle(...) 抽象方法签名(Update/Get/Scan/Cleanup/Rebase)。
SDK 支持
sdk/src/main/java/org/zstack/sdk/*, SourceClassMap.java
新增多项 SDK Action/Result/模型(Cleanup/Get/Register/Scan/Update 等)并更新类型映射。
测试/模拟器
testlib/src/main/java/org/zstack/testlib/*, plugin/*/LocalStorageSimulator*, NfsPrimaryStorageSpec.groovy
新增 testlib API helper、模拟器 HTTP 路径注册与返回对象以支持元数据测试场景。

Sequence Diagram(s)

sequenceDiagram
    participant API as API
    participant Flow as VmExpungeMetadataFlow
    participant DB as Database
    participant Bus as CloudBus
    participant PS as PrimaryStorageService
    participant Host as HostAgent

    API->>Flow: 调用清理/注册/扫描流程
    Flow->>DB: 查询 VM/volume/ps 信息
    Flow->>Bus: 发送 Cleanup/Get/Scan/Update 消息(目标 primaryStorageUuid)
    Bus->>PS: 路由到对应 PrimaryStorage 服务
    PS->>Host: HTTP 调用主机 Agent(get/scan/cleanup/write/rebase)
    Host-->>PS: 返回命令响应(metadata / entries / rebasedCount)
    PS-->>Bus: 回复 MessageReply(成功或 ErrorCode)
    Bus-->>Flow: 回调处理(成功记录/失败提交 GC)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 我在元数据林中蹦跳寻路,路径与前缀画成图,
扫描声里有主机回音,注册清理轻声互诉,
本地与 NFS 牵手并进,SDK 与测试也来鼓舞,
GC 小舞步耐心重试,元数据春意正烂漫。

✨ 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/ZSV-11559@@4

@MatheMatrix

Copy link
Copy Markdown
Owner

Comment from yaohua.wu:

Review: MR !9463 — ZSV-11559 【注册虚拟机】MetadataImpact (zstack)

关联 MR: premium !13330 | zstack-utility !6816
Jira: ZSV-11559 — New Feature: VM Registration
变更范围: 153 files — 新增 @MetadataImpact 注解体系、DB 实体、API 消息、扩展点、GC/Expunge 流程


🔴 Critical

1. [VmInstanceMetadataConstants.java] extractOldPrefix() 基于 marker 的前缀提取存在误匹配风险

该方法硬编码扫描 /rootVolumes//dataVolumes//volumeSnapshots//memory/ 等 marker 来截取路径前缀。如果 PS 挂载点本身包含这些字符串(如 /opt/rootVolumes/data/),extractOldPrefix() 会返回过短的前缀,导致 RebaseVolumeBackingFileOnPrimaryStorageMsg 使用错误的 old→new 映射,可能损坏 volume backing file chain,使 VM 无法启动

建议:从 MN 侧传入明确的 PS mount path 前缀,而非从文件路径反推。或至少从路径末端向前扫描(最后一个 marker 之前的部分),减小误匹配概率。

2. [VmExpungeMetadataFlow.java] hostUuid 可能为 null 且无保护

Lines 159-163: hostUuid 先取 vmSpec.getVmInventory().getHostUuid(),fallback 到 getLastHostUuid(),两者都可能为 null(VM 注册后从未启动即被删除的场景)。null hostUuid 传给 CleanupVmInstanceMetadataOnPrimaryStorageMsg 后,LocalStorage 类型的 PS 无法定位目标 host,agent 调用会失败。

建议:添加 null check,若 hostUuid 为 null 则 log warn 并跳过 cleanup(metadata 文件会随 PS 目录清理自然消失),或直接提交 GC 延迟处理。

3. [APICreateSystemTagMsg/APICreateSystemTagsMsg] resourceUuidToVmUuid resolver 作用域过宽

System tag 可以应用于任何资源类型(Host、Image、Network 等),不仅仅是 VM 及其关联资源。每次创建 system tag 时都会触发 resolver 做 DB 查询(检查 VM → Volume → NIC 链路),对非 VM 相关的高频 tag 操作造成不必要的性能开销。

建议:在 interceptor 层增加 resourceType 预检查,仅当 tag 的 resourceType 属于 VM 相关类型时才触发 resolver;或在 resolver 内部做 early return。


🟡 Warning

4. [V5.0.0__schema.sql] SQL migration 幂等性

CREATE TABLE 未使用 IF NOT EXISTS。如果 migration 重复执行(feature 分支合并顺序问题),会报错。建议加上 IF NOT EXISTS

5. [VmGlobalConfig.java] 6 个新 GlobalConfig 的默认值合理性

  • vm.metadata.flush.concurrency 默认值未在 diff 中可见,需确认是否为合理默认(建议 5-10)
  • vm.metadata.flush.batchSize 范围 1-1000,但与 LONGTEXTmetadataSnapshot 结合,高 batchSize + 大 VM 可能导致单次 flush 的内存压力较大

6. [VmMetadataFingerprintVO.java] metadataSnapshot 使用 LONGTEXT 无大小限制

VmMetadataErrors.METADATA_PAYLOAD_TOO_LARGE (1309) 错误码已定义,但本 MR 中未见对应的大小校验逻辑。对于拥有大量 snapshot 的 VM,序列化后的 metadata JSON 可能非常大。建议在 persist 前加上大小校验。

7. [@MetadataImpact resolver] resolver 名称为 magic string,无编译期校验

@MetadataImpact(resolver = "snapshotUuidToVmUuid") 中的 resolver 名称是 Spring bean name 字符串引用。如果 resolver bean 未注册(如社区版缺少 premium 模块),运行时会 silent fail 或异常。VmMetadataDirtyService 已正确使用 required = false,但 resolver lookup 需要相同的容错处理。

建议:定义 ResolverConstants 类集中管理 resolver 名称常量,或在 VmMetadataUpdateInterceptor.start() 中校验所有 resolver bean 是否存在。

8. [多个 API 消息文件] 部分文件 diff 为空

约 20+ 个文件标记为 MOD 但 diff 内容为空(如 APISetVmClockTrackMsg、多个 volume 消息等)。这些可能是仅添加了 @MetadataImpact 注解但 GitLab diff API 未正确返回。需确认这些文件的注解是否已正确添加。


🟢 Suggestion

9. [VmMetadataErrors.java] 错误码格式

使用 VM_METADATA.1300 格式的字符串错误码。建议确认与 ZStack 标准 @ErrorCode 注册机制的兼容性,确保客户端能正确区分这些错误。

10. [VmMetadataScanEntry.java] hostUuid 字段的语义

Scan 返回的 hostUuid 含义需明确——是 metadata 文件所在的 host(对 LocalStorage 有意义)还是 VM 最后运行的 host?建议在字段上加 JavaDoc 注释。

11. [CleanupVmInstanceMetadataOnPrimaryStorageGC.java] GC 设计

GC 在 triggerNow() 中先检查 PS 是否存在,不存在则 cancel()——这是正确的。但建议在 GC 日志中包含 vmUuid 和 psUuid,便于运维排查。


架构评价

整体设计合理:

  • @MetadataImpact 注解 + interceptor 的声明式脏标记机制,避免了在每个 API handler 中手动调用
  • ✅ Extension point 分离存储类型差异(Local/NFS/SharedBlock),符合 ZStack 插件架构
  • VmExpungeMetadataFlow 作为 NoRollbackFlow + GC fallback,cleanup 失败不阻塞 VM 删除
  • ✅ DB 外键 ON DELETE CASCADE / ON DELETE SET NULL 设计正确

主要风险点在于 extractOldPrefix() 的路径解析逻辑和 resolver 的运行时容错。

Verdict: REVISION_REQUIRED

Critical #1 (extractOldPrefix 误匹配风险) 和 #2 (hostUuid null 未保护) 需要修复后再合入。


🤖 Robot Reviewer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)

100-141: ⚠️ Potential issue | 🔴 Critical

把 4 个 metadata 本地消息也接到 handleLocalMessage(),否则新增 handler 不可达。

这里目前只把 RebaseVolumeBackingFileOnPrimaryStorageMsg 加进了分发链,Update/Get/Scan/CleanupVmInstanceMetadataOnPrimaryStorageMsg 都没有对应分支。发到 NfsPrimaryStorage 的这四类本地消息会继续落到 super.handleLocalMessage(msg),下面新增的处理逻辑实际上不会执行。

🐛 建议修复
         } else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) {
             handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg);
+        } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) {
+            handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg);
+        } else if (msg instanceof GetVmInstanceMetadataFromPrimaryStorageMsg) {
+            handle((GetVmInstanceMetadataFromPrimaryStorageMsg) msg);
+        } else if (msg instanceof ScanVmInstanceMetadataFromPrimaryStorageMsg) {
+            handle((ScanVmInstanceMetadataFromPrimaryStorageMsg) msg);
+        } else if (msg instanceof CleanupVmInstanceMetadataOnPrimaryStorageMsg) {
+            handle((CleanupVmInstanceMetadataOnPrimaryStorageMsg) msg);
         } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) {
             handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg);

Also applies to: 1933-2061

🤖 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 100 - 141, The message dispatch in
NfsPrimaryStorage.handleLocalMessage currently routes many message types but
misses four metadata local messages—UpdateVmInstanceMetadataOnPrimaryStorageMsg,
GetVmInstanceMetadataFromPrimaryStorageMsg,
ScanVmInstanceMetadataOnPrimaryStorageMsg,
CleanupVmInstanceMetadataOnPrimaryStorageMsg—so their handlers never run; update
handleLocalMessage to add else-if branches for each of these four message
classes and call the existing handle(...) methods for them (matching the pattern
used for RebaseVolumeBackingFileOnPrimaryStorageMsg and others) so those
messages are dispatched to their handlers instead of falling through to
super.handleLocalMessage(msg).
🧹 Nitpick comments (12)
header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java (1)

7-20: 建议将字段 uuid 重命名为 vmInstanceUuid 以提高代码可读性。

当前字段名 uuid 语义不够明确,而接口方法 getVmInstanceUuid() 清楚表明这是 VM 实例的 UUID。将字段名与接口方法保持一致,可以使代码更具自解释性。

♻️ 建议的重构
 public class ReadVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage {
-    private String uuid;
+    private String vmInstanceUuid;

-    public String getUuid() {
-        return uuid;
+    public String getVmInstanceUuid() {
+        return vmInstanceUuid;
     }

-    public void setUuid(String uuid) {
-        this.uuid = uuid;
+    public void setVmInstanceUuid(String vmInstanceUuid) {
+        this.vmInstanceUuid = vmInstanceUuid;
     }

-    `@Override`
-    public String getVmInstanceUuid() {
-        return uuid;
-    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java`
around lines 7 - 20, Rename the private field uuid in ReadVmInstanceMetadataMsg
to vmInstanceUuid and update the corresponding accessor methods: change
getUuid() to return vmInstanceUuid (or keep getUuid() as a delegating method if
needed for binary compatibility), change setUuid(String) to
setVmInstanceUuid(String) (or adapt setUuid to delegate), and update
getVmInstanceUuid() to return vmInstanceUuid; ensure all references inside
ReadVmInstanceMetadataMsg and any usages elsewhere are updated to the new
field/method names (getVmInstanceUuid, setVmInstanceUuid, vmInstanceUuid) so the
field name matches the interface method for clarity.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java (3)

18-18: 建议将 code 字段声明为 final

该字段在构造器中被赋值后不再修改,应该标记为 final 以确保不可变性和线程安全性。

♻️ 建议的修改
-    private String code;
+    private final String code;
🤖 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/VmMetadataErrors.java` at
line 18, 将 VmMetadataErrors 类中的私有字段 code 声明为 final,因为它仅在构造器中赋值且之后不再修改;在
VmMetadataErrors 的字段定义处将 "private String code;" 改为 "private final String code;"
并确保所有构造器对 code 的赋值保持不变(如果存在默认构造器需改为显式赋值或移除),以保证不可变性和线程安全性。

11-11: 考虑优化 METADATA_VM_REGISTERING 的命名。

该常量名称看起来更像描述一个状态("VM 正在注册中")而非错误条件。其他错误常量如 METADATA_INVALID_FORMATMETADATA_NOT_FOUND 等都明确表示错误场景。

如果该错误码的含义是"由于 VM 正在注册而导致操作不被允许",可以考虑使用更明确的名称,例如:

  • METADATA_VM_REGISTRATION_IN_PROGRESS
  • METADATA_OPERATION_BLOCKED_BY_REGISTRATION

这样能更清晰地表达错误语义。

🤖 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/VmMetadataErrors.java` at
line 11, Rename the enum constant METADATA_VM_REGISTERING to a clearer error
name that expresses the blocking-by-registration semantics (e.g.,
METADATA_VM_REGISTRATION_IN_PROGRESS or
METADATA_OPERATION_BLOCKED_BY_REGISTRATION); update the enum declaration
(replace METADATA_VM_REGISTERING(1307) with the chosen identifier) and then
search & update all references/usages of METADATA_VM_REGISTERING across the
codebase (tests, logging, exception throws, switch statements) to use the new
constant name so compilation and behavior remain consistent.

3-15: 建议为枚举类及常量添加 Javadoc 注释。

该枚举位于 header 包中,属于公共 API 的一部分。根据编码规范,公共 API 应配有有效的 Javadoc 注释以便生成 API 文档。建议:

  1. VmMetadataErrors 枚举添加类级别的 Javadoc,说明该枚举的用途
  2. 为各个错误常量添加注释,说明每个错误码的含义及触发场景

这将有助于 API 文档的生成和开发者理解。

📝 Javadoc 示例
+/**
+ * VM 实例元数据相关的错误码定义。
+ * <p>
+ * 每个错误码对应一个特定的元数据操作失败场景。
+ */
 public enum VmMetadataErrors {
+    /** 元数据格式无效 */
     METADATA_INVALID_FORMAT(1300),
+    /** 元数据 schema 版本不匹配 */
     METADATA_SCHEMA_VERSION_MISMATCH(1301),
+    /** 元数据 UUID 冲突 */
     METADATA_UUID_CONFLICT(1302),
     // ... 其他常量
🤖 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/VmMetadataErrors.java`
around lines 3 - 15, 为公共枚举 VmMetadataErrors 添加类级别 Javadoc,简要说明该枚举表示 VM
元数据相关的错误码集合及使用场景;同时为每个枚举常量(如 METADATA_INVALID_FORMAT,
METADATA_SCHEMA_VERSION_MISMATCH, METADATA_UUID_CONFLICT,
METADATA_STORAGE_NOT_SUPPORTED, METADATA_CROSS_STORAGE_FORBIDDEN,
METADATA_INSTALL_PATH_NOT_FOUND, METADATA_CACHE_VM_NOT_REGISTERABLE,
METADATA_VM_REGISTERING, METADATA_READ_CORRUPTED, METADATA_PAYLOAD_TOO_LARGE,
METADATA_PS_UNREACHABLE, METADATA_FEATURE_DISABLED)添加单独的 Javadoc
注释,描述错误含义、触发条件和可能的处理建议(简短一两句),使用标准 Javadoc 注释格式 (/** ... */) 放在枚举声明和各常量前以便生成 API
文档。
header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java (1)

3-7: 考虑为 vo 字段添加注释说明其含义。

字段 vo 命名较为简短,虽然在 ZStack 代码库中 VO 通常表示 "Value Object",但添加简要注释可提高代码可读性,便于其他开发者理解其用途。

📝 建议添加字段注释
 public class ResourceMetadata {
     private String resourceUuid;
+    /** 资源对应的 VO (Value Object) 序列化数据 */
     private String vo;
+    /** 资源关联的系统标签 JSON 数据 */
     private String systemTags;
+    /** 资源配置 JSON 数据 */
     private String resourceConfigs;
🤖 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 3 - 7, Add a brief Javadoc/comment for the vo field in class
ResourceMetadata explaining what "vo" stands for and its intended semantic
(e.g., "Value Object representing ..."), so callers and maintainers understand
its purpose; update the declaration of the vo field in ResourceMetadata to
include that comment (use the class name ResourceMetadata and field name vo to
locate it).
header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java (1)

5-14: 字段命名与相关类存在轻微不一致。

此类使用 metadata 字段名,而 ReadVmInstanceMetadataReply 使用 vmMetadata。虽然不影响功能,但为了代码一致性和可维护性,建议统一命名风格。

🤖 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/GetVmInstanceMetadataFromPrimaryStorageReply.java`
around lines 5 - 14, GetVmInstanceMetadataFromPrimaryStorageReply uses the field
and accessors named metadata (field metadata, methods getMetadata/setMetadata)
which is inconsistent with ReadVmInstanceMetadataReply's vmMetadata; rename the
field to vmMetadata and update the accessors to getVmMetadata/setVmMetadata (or
getVmMetadata/setVmMetadata to match exact style in
ReadVmInstanceMetadataReply), and update all call sites and serialization usages
that reference metadata to the new vmMetadata names so the classes are
consistent.
sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java (1)

79-85: Boolean getter 命名建议

Java Bean 规范中,boolean 类型字段的 getter 方法通常使用 is 前缀而非 get 前缀。

♻️ 建议的修改
-    public boolean getIncomplete() {
+    public boolean isIncomplete() {
         return this.incomplete;
     }
🤖 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/VmMetadataScanEntry.java` around lines 79 -
85, The boolean getter for the field "incomplete" in class VmMetadataScanEntry
should follow JavaBean conventions: replace the current getIncomplete() with an
isIncomplete() method (or add isIncomplete() and mark getIncomplete() as
`@Deprecated/forwarding` to isIncomplete() to preserve backward compatibility);
update or add Javadoc accordingly and ensure any serializers/reflection usages
reference the new isIncomplete() accessor while leaving setIncomplete(boolean)
unchanged.
conf/db/upgrade/V5.0.0__schema.sql (1)

9-10: 建议为时间列显式设置 DEFAULT CURRENT_TIMESTAMP

当前写法依赖数据库隐式行为,跨 MySQL/MariaDB 配置时可读性与一致性较弱,建议显式声明默认值。

建议修改
-    `lastOpDate` timestamp on update CURRENT_TIMESTAMP,
-    `createDate` timestamp,
+    `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `createDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

As per coding guidelines "Do not use DEFAULT 0000-00-00 00:00:00 , use DEFAULT CURRENT_TIMESTAMP instead".

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

In `@conf/db/upgrade/V5.0.0__schema.sql` around lines 9 - 10, Update the timestamp
column definitions to declare explicit defaults: change the `lastOpDate` column
(currently `timestamp on update CURRENT_TIMESTAMP`) to include DEFAULT
CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP, and set `createDate`
(currently `timestamp`) to DEFAULT CURRENT_TIMESTAMP; modify the column
definitions for `lastOpDate` and `createDate` accordingly so they use DEFAULT
CURRENT_TIMESTAMP instead of relying on implicit behavior.
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java (1)

15-25: 建议为 extractOldPrefix 补充语义注释或单测示例。

当前返回值是按 marker 截断得到“前缀”而非包含 marker,本身可行,但语义不够直观;补充注释/示例能明显降低后续误用风险。

🤖 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/VmInstanceMetadataConstants.java`
around lines 15 - 25, Add a clear JavaDoc to
VmInstanceMetadataConstants.extractOldPrefix explaining its exact behavior
(inputs handled: null or not starting with "/", how it searches
STORAGE_PATH_MARKERS, and that it returns the substring up to idx+1 when a
marker is found or null otherwise) and add unit tests for extractOldPrefix that
cover null input, paths not starting with "/", paths with a marker in the
middle, markers at start/end, and multiple markers to show the chosen cut point;
name tests descriptively (e.g., extractOldPrefix_nullInput_returnsNull,
extractOldPrefix_noLeadingSlash_returnsNull,
extractOldPrefix_markerInMiddle_returnsExpectedPrefix) so future readers can
find intent and avoid misuse.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)

7-10: 请给这个 SPI 补上 Javadoc。

这是跨主存储后端的扩展点,getPrimaryStorageType()calculatePathReplacements(...) 的输入/输出约束现在只能靠调用方猜,后续实现很容易出现行为不一致。

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 7 - 10, 为 VmMetadataPathReplacementExtensionPoint 接口补充完整
Javadoc:在接口注释中说明这是用于跨主存储后端计算 VM 元数据路径替换的扩展点,并描述调用语义;为 getPrimaryStorageType()
添加注释说明返回值是该扩展点适用的主存储类型(非空、大小写约定等);为 calculatePathReplacements(String
targetPsUuid, List<String> allOldPaths) 添加注释说明各参数含义(targetPsUuid 为目标主存储
UUID,allOldPaths 为需替换的原始路径列表)、输入约束(可否为 null 或空、是否可变)、返回值语义(PathReplacementResult
表示替换结果、何时返回空或空列表、是否保留顺序)以及线程/并发和异常行为;为内部静态类 PathReplacementResult
补充注释,说明其字段含义、不变性和使用场景。涉及符号:VmMetadataPathReplacementExtensionPoint、getPrimaryStorageType、calculatePathReplacements、PathReplacementResult。
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)

37-37: 建议使用泛型类型参数。

Map data 使用了原始类型,建议改为 Map<String, Object> data 以提高类型安全性。

🤖 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 37, The run method in VmExpungeMetadataFlow currently uses a raw Map
parameter; change the signature of public void run(FlowTrigger trigger, Map
data) to use generics: public void run(FlowTrigger trigger, Map<String, Object>
data), update any internal usages/casts accordingly (e.g., references to
data.get(...) should assume Object or cast to expected types), and add necessary
imports if missing to ensure type-safety across the method.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)

944-952: 冗余的全限定类名。

在类内部定义的嵌套类中,使用 LocalStorageKvmBackend.AgentCommandLocalStorageKvmBackend.AgentResponse 是冗余的,可以直接使用 AgentCommandAgentResponse

♻️ 建议的修改
-    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 944 - 952, The nested classes PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp use redundant fully-qualified types
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse;
change their declarations to extend/declare AgentCommand and AgentResponse
directly (i.e., replace LocalStorageKvmBackend.AgentCommand with AgentCommand
and LocalStorageKvmBackend.AgentResponse with AgentResponse) so the nested
classes read as public static class PrefixRebaseBackingFilesCmd extends
AgentCommand { ... } and public static class PrefixRebaseBackingFilesRsp extends
AgentResponse { ... }.
🤖 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/VmExpungeMetadataFlow.java`:
- Around line 89-93: VmExpungeMetadataFlow currently sets
cmsg.setHostUuid(hostUuid) even when hostUuid is null; update the flow to check
the resolved hostUuid (from spec.getVmInventory().getHostUuid() or
getLastHostUuid()) and if it is null do not set the host UUID and instead
short-circuit: log a warning and submit the GC/cleanup task directly or skip
sending the CleanupVmInstanceMetadataOnPrimaryStorageMsg to avoid confusing
handlers like LocalStorageBase.handle; locate the logic around
VmExpungeMetadataFlow, the spec.getVmInventory() usage, and the cmsg.setHostUuid
call and add the null check + fallback behavior there.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`:
- Around line 10-17: Update the REST path to include the vm instance UUID as a
path variable by adding an optional path with {uuid} to the `@RestRequest` on
class APIGetVmInstanceMetadataFromPrimaryStorageMsg; specifically, add
optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"} in the
`@RestRequest` annotation so the {uuid} placeholder maps to the existing private
String uuid field (annotated with `@APIParam`) and aligns with the project's
path-variable convention used by messages like
APIQueryVmInstanceResourceMetadataGroupMsg.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`:
- Around line 29-39: The interceptor handling
APIRegisterVmInstanceFromMetadataMsg must validate placement consistency: when
processing APIRegisterVmInstanceFromMetadataMsg (fields hostUuid, clusterUuid,
zoneUuid, primaryStorageUuid) fetch HostVO, ClusterVO, ZoneVO and
PrimaryStorage/PrimaryStorageClusterRef and enforce that the host's clusterUuid
equals the message.clusterUuid, the cluster's zoneUuid equals message.zoneUuid,
and that primaryStorageUuid is mounted to the cluster (exists a
PrimaryStorageClusterRefVO linking primaryStorageUuid to clusterUuid); if any
check fails throw an APIMessageInterceptionException with a clear error. Ensure
these checks are added into the existing interceptor entry point (e.g.,
intercept or validate method handling APIRegisterVmInstanceFromMetadataMsg) and
use the VO/Ref lookups used elsewhere in the codebase for consistency.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy`:
- Line 20: The empty desc string in
APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy should be filled with a concise
Chinese description matching the API's behavior; update the desc """""" in the
APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy file to a short sentence that
explains what APIUpdateVmInstanceMetadataMsg does (e.g., it updates VM instance
metadata, which keys are supported and whether it overwrites existing entries or
merges), ensuring the text is accurate and consistent with the implementation
and other language docs.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1989-1997: The call to
factory.getConnectedHostForOperation(getSelfInventory()) can throw
OperationFailureException (it is already handled elsewhere), so in each of the
four handlers (handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg), the Scan
handler, the Cleanup handler and the Rebase handler) you must stop treating the
API as "returns empty list" and instead wrap the
getConnectedHostForOperation(...) call in a try/catch for
OperationFailureException; on catching it construct the appropriate reply (e.g.
GetVmInstanceMetadataFromPrimaryStorageReply / corresponding Scan/Cleanup/Rebase
reply), set reply.setError(operr(e, "...")) or use e.getMessage() for context,
call bus.reply(msg, reply) and return — remove or ignore the subsequent
isEmpty() branch because it will not be reached when the API throws.

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1866: The code in PrimaryStorageBase uses
Q.New(...).findValue() to get psType and then calls
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
and reports an error if ext==null, but findValue() can return null when the
primary storage was deleted; update the flow to first check the existence of the
PrimaryStorageVO result (e.g., assign the result of findValue() to psType and if
psType==null) and handle the deleted/absent primary storage case by setting an
appropriate error on evt (using evt.setError with a clear "primary storage not
found" message referencing msg.getPrimaryStorageUuid()) before calling
getExtensionFromMap, so getExtensionFromMap is never invoked with a null type
and the error becomes accurate instead of "type null does not support metadata".
- Around line 1790-1818: CleanupVmInstanceMetadataOnPrimaryStorageGC currently
sends CleanupVmInstanceMetadataOnPrimaryStorageMsg to every primary storage,
causing repeated "operation not supported" replies and GC retries; modify
CleanupVmInstanceMetadataOnPrimaryStorageGC.triggerNow to first filter target
primary storages by checking they are of a supported type and that a
VmMetadataPathBuildExtensionPoint implementation exists for them (reuse the same
check used in VmExpungeMetadataFlow), and only send
CleanupVmInstanceMetadataOnPrimaryStorageMsg to those primaries; alternatively,
skip scheduling GC for primaries that would fall back to PrimaryStorageBase
handlers (which return operr) so you avoid futile retries.

---

Outside diff comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 100-141: The message dispatch in
NfsPrimaryStorage.handleLocalMessage currently routes many message types but
misses four metadata local messages—UpdateVmInstanceMetadataOnPrimaryStorageMsg,
GetVmInstanceMetadataFromPrimaryStorageMsg,
ScanVmInstanceMetadataOnPrimaryStorageMsg,
CleanupVmInstanceMetadataOnPrimaryStorageMsg—so their handlers never run; update
handleLocalMessage to add else-if branches for each of these four message
classes and call the existing handle(...) methods for them (matching the pattern
used for RebaseVolumeBackingFileOnPrimaryStorageMsg and others) so those
messages are dispatched to their handlers instead of falling through to
super.handleLocalMessage(msg).

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Line 37: The run method in VmExpungeMetadataFlow currently uses a raw Map
parameter; change the signature of public void run(FlowTrigger trigger, Map
data) to use generics: public void run(FlowTrigger trigger, Map<String, Object>
data), update any internal usages/casts accordingly (e.g., references to
data.get(...) should assume Object or cast to expected types), and add necessary
imports if missing to ensure type-safety across the method.

In `@conf/db/upgrade/V5.0.0__schema.sql`:
- Around line 9-10: Update the timestamp column definitions to declare explicit
defaults: change the `lastOpDate` column (currently `timestamp on update
CURRENT_TIMESTAMP`) to include DEFAULT CURRENT_TIMESTAMP and ON UPDATE
CURRENT_TIMESTAMP, and set `createDate` (currently `timestamp`) to DEFAULT
CURRENT_TIMESTAMP; modify the column definitions for `lastOpDate` and
`createDate` accordingly so they use DEFAULT CURRENT_TIMESTAMP instead of
relying on implicit behavior.

In
`@header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java`:
- Around line 5-14: GetVmInstanceMetadataFromPrimaryStorageReply uses the field
and accessors named metadata (field metadata, methods getMetadata/setMetadata)
which is inconsistent with ReadVmInstanceMetadataReply's vmMetadata; rename the
field to vmMetadata and update the accessors to getVmMetadata/setVmMetadata (or
getVmMetadata/setVmMetadata to match exact style in
ReadVmInstanceMetadataReply), and update all call sites and serialization usages
that reference metadata to the new vmMetadata names so the classes are
consistent.

In
`@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java`:
- Around line 7-20: Rename the private field uuid in ReadVmInstanceMetadataMsg
to vmInstanceUuid and update the corresponding accessor methods: change
getUuid() to return vmInstanceUuid (or keep getUuid() as a delegating method if
needed for binary compatibility), change setUuid(String) to
setVmInstanceUuid(String) (or adapt setUuid to delegate), and update
getVmInstanceUuid() to return vmInstanceUuid; ensure all references inside
ReadVmInstanceMetadataMsg and any usages elsewhere are updated to the new
field/method names (getVmInstanceUuid, setVmInstanceUuid, vmInstanceUuid) so the
field name matches the interface method for clarity.

In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`:
- Around line 3-7: Add a brief Javadoc/comment for the vo field in class
ResourceMetadata explaining what "vo" stands for and its intended semantic
(e.g., "Value Object representing ..."), so callers and maintainers understand
its purpose; update the declaration of the vo field in ResourceMetadata to
include that comment (use the class name ResourceMetadata and field name vo to
locate it).

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java`:
- Around line 15-25: Add a clear JavaDoc to
VmInstanceMetadataConstants.extractOldPrefix explaining its exact behavior
(inputs handled: null or not starting with "/", how it searches
STORAGE_PATH_MARKERS, and that it returns the substring up to idx+1 when a
marker is found or null otherwise) and add unit tests for extractOldPrefix that
cover null input, paths not starting with "/", paths with a marker in the
middle, markers at start/end, and multiple markers to show the chosen cut point;
name tests descriptively (e.g., extractOldPrefix_nullInput_returnsNull,
extractOldPrefix_noLeadingSlash_returnsNull,
extractOldPrefix_markerInMiddle_returnsExpectedPrefix) so future readers can
find intent and avoid misuse.

In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java`:
- Line 18: 将 VmMetadataErrors 类中的私有字段 code 声明为 final,因为它仅在构造器中赋值且之后不再修改;在
VmMetadataErrors 的字段定义处将 "private String code;" 改为 "private final String code;"
并确保所有构造器对 code 的赋值保持不变(如果存在默认构造器需改为显式赋值或移除),以保证不可变性和线程安全性。
- Line 11: Rename the enum constant METADATA_VM_REGISTERING to a clearer error
name that expresses the blocking-by-registration semantics (e.g.,
METADATA_VM_REGISTRATION_IN_PROGRESS or
METADATA_OPERATION_BLOCKED_BY_REGISTRATION); update the enum declaration
(replace METADATA_VM_REGISTERING(1307) with the chosen identifier) and then
search & update all references/usages of METADATA_VM_REGISTERING across the
codebase (tests, logging, exception throws, switch statements) to use the new
constant name so compilation and behavior remain consistent.
- Around line 3-15: 为公共枚举 VmMetadataErrors 添加类级别 Javadoc,简要说明该枚举表示 VM
元数据相关的错误码集合及使用场景;同时为每个枚举常量(如 METADATA_INVALID_FORMAT,
METADATA_SCHEMA_VERSION_MISMATCH, METADATA_UUID_CONFLICT,
METADATA_STORAGE_NOT_SUPPORTED, METADATA_CROSS_STORAGE_FORBIDDEN,
METADATA_INSTALL_PATH_NOT_FOUND, METADATA_CACHE_VM_NOT_REGISTERABLE,
METADATA_VM_REGISTERING, METADATA_READ_CORRUPTED, METADATA_PAYLOAD_TOO_LARGE,
METADATA_PS_UNREACHABLE, METADATA_FEATURE_DISABLED)添加单独的 Javadoc
注释,描述错误含义、触发条件和可能的处理建议(简短一两句),使用标准 Javadoc 注释格式 (/** ... */) 放在枚举声明和各常量前以便生成 API
文档。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 7-10: 为 VmMetadataPathReplacementExtensionPoint 接口补充完整
Javadoc:在接口注释中说明这是用于跨主存储后端计算 VM 元数据路径替换的扩展点,并描述调用语义;为 getPrimaryStorageType()
添加注释说明返回值是该扩展点适用的主存储类型(非空、大小写约定等);为 calculatePathReplacements(String
targetPsUuid, List<String> allOldPaths) 添加注释说明各参数含义(targetPsUuid 为目标主存储
UUID,allOldPaths 为需替换的原始路径列表)、输入约束(可否为 null 或空、是否可变)、返回值语义(PathReplacementResult
表示替换结果、何时返回空或空列表、是否保留顺序)以及线程/并发和异常行为;为内部静态类 PathReplacementResult
补充注释,说明其字段含义、不变性和使用场景。涉及符号:VmMetadataPathReplacementExtensionPoint、getPrimaryStorageType、calculatePathReplacements、PathReplacementResult。

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 944-952: The nested classes PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp use redundant fully-qualified types
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse;
change their declarations to extend/declare AgentCommand and AgentResponse
directly (i.e., replace LocalStorageKvmBackend.AgentCommand with AgentCommand
and LocalStorageKvmBackend.AgentResponse with AgentResponse) so the nested
classes read as public static class PrefixRebaseBackingFilesCmd extends
AgentCommand { ... } and public static class PrefixRebaseBackingFilesRsp extends
AgentResponse { ... }.

In `@sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java`:
- Around line 79-85: The boolean getter for the field "incomplete" in class
VmMetadataScanEntry should follow JavaBean conventions: replace the current
getIncomplete() with an isIncomplete() method (or add isIncomplete() and mark
getIncomplete() as `@Deprecated/forwarding` to isIncomplete() to preserve backward
compatibility); update or add Javadoc accordingly and ensure any
serializers/reflection usages reference the new isIncomplete() accessor while
leaving setIncomplete(boolean) unchanged.
🪄 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: 8eb20e06-7aec-44ad-815b-7a98636003b5

📥 Commits

Reviewing files that changed from the base of the PR and between 5924b9d and dfbb104.

⛔ 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 (148)
  • 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/upgrade/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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • 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/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/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/APIDeleteVmNicMsg.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/APIExpungeVmInstanceMsg.java
  • 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/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/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/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • 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/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.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 +89 to +93
String hostUuid = spec.getVmInventory().getHostUuid();
if (hostUuid == null) {
hostUuid = spec.getVmInventory().getLastHostUuid();
}
cmsg.setHostUuid(hostUuid);

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

hostUuid 可能为 null,但仍会设置到消息中。

spec.getVmInventory().getHostUuid()spec.getVmInventory().getLastHostUuid() 都为 null 时,cmsg.setHostUuid(null) 会被调用。这可能导致下游处理器(如 LocalStorageBase.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg))无法确定目标主机。

建议在发送消息前检查 hostUuid 是否为 null,如果为 null 则直接提交 GC 任务或记录日志并跳过。

🛠️ 建议的修复方案
         String hostUuid = spec.getVmInventory().getHostUuid();
         if (hostUuid == null) {
             hostUuid = spec.getVmInventory().getLastHostUuid();
         }
+        if (hostUuid == null) {
+            logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no host or lastHost, " +
+                    "submitting GC job for retry", vmUuid));
+            submitGC(psUuid, vmUuid, rootVolumeUuid, metadataPath, null);
+            trigger.next();
+            return;
+        }
         cmsg.setHostUuid(hostUuid);
🤖 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 89 - 93, VmExpungeMetadataFlow currently sets
cmsg.setHostUuid(hostUuid) even when hostUuid is null; update the flow to check
the resolved hostUuid (from spec.getVmInventory().getHostUuid() or
getLastHostUuid()) and if it is null do not set the host UUID and instead
short-circuit: log a warning and submit the GC/cleanup task directly or skip
sending the CleanupVmInstanceMetadataOnPrimaryStorageMsg to avoid confusing
handlers like LocalStorageBase.handle; locate the logic around
VmExpungeMetadataFlow, the spec.getVmInventory() usage, and the cmsg.setHostUuid
call and add the null check + fallback behavior there.

Comment on lines +10 to +17
@RestRequest(
path = "/primary-storage/vm-instances/metadata",
method = HttpMethod.GET,
responseClass = APIGetVmInstanceMetadataFromPrimaryStorageEvent.class
)
public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APIMessage implements VmInstanceMessage {
@APIParam(resourceType = VmInstanceVO.class)
private 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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查项目中类似 GET metadata API 的路径设计模式
rg -n '@RestRequest' --type=java -A 5 | rg -B 3 'metadata' | head -50

Repository: MatheMatrix/zstack

Length of output: 5222


🏁 Script executed:

cat -n /dev/stdin << 'EOF' | head -40
header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
EOF
# First, let's see the full file
fd 'APIGetVmInstanceMetadataFromPrimaryStorageMsg.java' --type f -x cat -n {} | head -50

Repository: MatheMatrix/zstack

Length of output: 1533


🏁 Script executed:

# Check the full content of the target file
find . -name 'APIGetVmInstanceMetadataFromPrimaryStorageMsg.java' -type f -exec cat -n {} \;

Repository: MatheMatrix/zstack

Length of output: 1473


🏁 Script executed:

# Check APIScanVmInstanceMetadataFromPrimaryStorageMsg for comparison
find . -name 'APIScanVmInstanceMetadataFromPrimaryStorageMsg.java' -type f -exec cat -n {} \;

Repository: MatheMatrix/zstack

Length of output: 1378


🏁 Script executed:

# Check one of the similar metadata query APIs that has optionalPaths
find . -name 'APIQueryVmInstanceResourceMetadataGroupMsg.java' -type f -exec cat -n {} \;

Repository: MatheMatrix/zstack

Length of output: 1145


REST 路径设计需要调整

API 路径应包含 UUID 的路径变量。建议修改为支持 /primary-storage/vm-instances/{uuid}/metadata 的模式,与项目中类似的查询 API(如 APIQueryVmInstanceResourceMetadataGroupMsg)保持一致,并符合编码规范中"当 path 中引用消息类变量时,使用 {variableName} 格式"的要求。

可在 @RestRequest 中添加 optionalPaths 属性:

`@RestRequest`(
        path = "/primary-storage/vm-instances/metadata",
        optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"},
        method = HttpMethod.GET,
        responseClass = APIGetVmInstanceMetadataFromPrimaryStorageEvent.class
)
🤖 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/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`
around lines 10 - 17, Update the REST path to include the vm instance UUID as a
path variable by adding an optional path with {uuid} to the `@RestRequest` on
class APIGetVmInstanceMetadataFromPrimaryStorageMsg; specifically, add
optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"} in the
`@RestRequest` annotation so the {uuid} placeholder maps to the existing private
String uuid field (annotated with `@APIParam`) and aligns with the project's
path-variable convention used by messages like
APIQueryVmInstanceResourceMetadataGroupMsg.

Comment on lines +29 to +39
@APIParam(resourceType = PrimaryStorageVO.class)
private String primaryStorageUuid;

@APIParam(resourceType = ZoneVO.class)
private String zoneUuid;

@APIParam(resourceType = ClusterVO.class)
private String clusterUuid;

@APIParam(resourceType = HostVO.class)
private String hostUuid;

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 '=== APIRegisterVmInstanceFromMetadataMsg validation in interceptor(s) ==='
rg -n -C4 --type=java \
  'APIRegisterVmInstanceFromMetadataMsg|validateMetadataPath|primaryStorageUuid|zoneUuid|clusterUuid|hostUuid' \
  compute header

echo
echo '=== Topology consistency checks around host/cluster/zone/primary storage ==='
rg -n -C4 --type=java \
  'attached to cluster|belongs to cluster|belongs to zone|primaryStorageUuid|clusterUuid|zoneUuid|hostUuid' \
  compute storage header

Repository: MatheMatrix/zstack

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== APIRegisterVmInstanceFromMetadataMsg class definition and inheritance ==="
fd -e java "APIRegisterVmInstanceFromMetadataMsg" header compute | \
  xargs -I {} sh -c 'echo "File: {}"; head -50 "{}"' 2>/dev/null | head -100

echo
echo "=== Search for validation in VmInstanceApiInterceptor for this API ==="
fd -e java "VmInstanceApiInterceptor" compute | \
  xargs -I {} sh -c 'grep -n "APIRegisterVmInstanceFromMetadataMsg\|validateMetadata\|validatePlacement" "{}" || true'

echo
echo "=== Search for NewVmInstanceMessage2 interface and its implementations ==="
rg -n "interface NewVmInstanceMessage2|class.*implements.*NewVmInstanceMessage2" --type=java -A 3 | head -80

echo
echo "=== Search for primaryStorageUuid validation with cluster/zone context ==="
rg -n "primaryStorageUuid.*cluster|cluster.*primaryStorageUuid|validatePrimaryStorage" --type=java -B2 -A2 | head -150

Repository: MatheMatrix/zstack

Length of output: 24437


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Complete validate(APIRegisterVmInstanceFromMetadataMsg msg) method ==="
file="compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java"
if [ -f "$file" ]; then
  wc -l "$file"
  echo "---"
  sed -n '1320,1380p' "$file"
fi

Repository: MatheMatrix/zstack

Length of output: 1712


缺少对放置关系一致性的校验。

当前拦截器仅校验 UUID 存在性和元数据路径有效性,未验证 hostUuidclusterUuidzoneUuid 之间的关联关系,也未验证 primaryStorageUuid 是否已挂载到指定的 clusterUuid。这些校验应当在 API 拦截阶段明确执行,否则请求会在更深的注册流程中才失败,导致回滚困难。

建议在拦截器中补充:

  • hostUuid 所属 clusterUuid 与消息提供的 clusterUuid 一致性检查
  • clusterUuid 所属 zoneUuid 与消息提供的 zoneUuid 一致性检查
  • primaryStorageUuid 已挂载到该 clusterUuid 的附加关系检查
🤖 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/APIRegisterVmInstanceFromMetadataMsg.java`
around lines 29 - 39, The interceptor handling
APIRegisterVmInstanceFromMetadataMsg must validate placement consistency: when
processing APIRegisterVmInstanceFromMetadataMsg (fields hostUuid, clusterUuid,
zoneUuid, primaryStorageUuid) fetch HostVO, ClusterVO, ZoneVO and
PrimaryStorage/PrimaryStorageClusterRef and enforce that the host's clusterUuid
equals the message.clusterUuid, the cluster's zoneUuid equals message.zoneUuid,
and that primaryStorageUuid is mounted to the cluster (exists a
PrimaryStorageClusterRefVO linking primaryStorageUuid to clusterUuid); if any
check fails throw an APIMessageInterceptionException with a clear error. Ensure
these checks are added into the existing interceptor entry point (e.g.,
intercept or validate method handling APIRegisterVmInstanceFromMetadataMsg) and
use the VO/Ref lookups used elsewhere in the codebase for consistency.

Comment on lines +1989 to +1997
protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) {
GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
if (connectedHosts.isEmpty()) {
reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
bus.reply(msg, reply);
return;
}
String hostUuid = connectedHosts.get(0).getUuid();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

不要把 getConnectedHostForOperation() 当成“返回空列表”的 API。

同文件 Line 461 已经对这个调用做了 OperationFailureException 捕获,说明它在无可用主机时会直接抛异常。这里四个新 handler 都是先调用再判断 isEmpty(),你们准备的 reply.setError(...) 分支在无主机场景下很可能根本走不到。

🐛 建议修复
-        List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
-        if (connectedHosts.isEmpty()) {
+        List<HostInventory> connectedHosts;
+        try {
+            connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+        } catch (OperationFailureException e) {
             reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
             bus.reply(msg, reply);
             return;
-        }
+        }

以上模式需要在 Get/Scan/Cleanup/Rebase 四个 handler 里一起套用。

Also applies to: 2016-2023, 2041-2048, 2066-2073

🤖 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 1989 - 1997, The call to
factory.getConnectedHostForOperation(getSelfInventory()) can throw
OperationFailureException (it is already handled elsewhere), so in each of the
four handlers (handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg), the Scan
handler, the Cleanup handler and the Rebase handler) you must stop treating the
API as "returns empty list" and instead wrap the
getConnectedHostForOperation(...) call in a try/catch for
OperationFailureException; on catching it construct the appropriate reply (e.g.
GetVmInstanceMetadataFromPrimaryStorageReply / corresponding Scan/Cleanup/Rebase
reply), set reply.setError(operr(e, "...")) or use e.getMessage() for context,
call bus.reply(msg, reply) and return — remove or ignore the subsequent
isEmpty() branch because it will not be reached when the API throws.

Comment on lines +1790 to +1818
protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) {
UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply();
reply.setError(operr("operation not supported"));
bus.reply(msg, reply);
}

protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) {
GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply();
reply.setError(operr("operation not supported"));
bus.reply(msg, reply);
}

protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) {
ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply();
reply.setError(operr("operation not supported"));
bus.reply(msg, reply);
}

protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) {
CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
reply.setError(operr("operation not supported"));
bus.reply(msg, reply);
}

protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) {
RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply();
reply.setError(operr("operation not supported"));
bus.reply(msg, reply);
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Metadata handlers implemented outside PrimaryStorageBase ==='
rg -n -C2 --type=java \
  'handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|RebaseVolumeBackingFileOnPrimaryStorageMsg)\s+msg\)' \
  storage plugin header

echo
echo '=== Metadata message call sites ==='
rg -n -C2 --type=java \
  'new (UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|RebaseVolumeBackingFileOnPrimaryStorageMsg)\(' \
  compute storage plugin header

Repository: MatheMatrix/zstack

Length of output: 9995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== All PrimaryStorageBase subclasses ==='
rg -n --type=java 'class \w+\s+extends\s+(PrimaryStorageBase|LocalStorageBase)' \
  plugin storage

echo
echo '=== LocalStorageBase handler overrides ==='
rg -n --type=java '@Override.*\n.*protected void handle' \
  plugin/localstorage -A2 | head -80

echo
echo '=== CleanupVmInstanceMetadataOnPrimaryStorageGC filter logic ==='
cat -n plugin/compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java | head -60

Repository: MatheMatrix/zstack

Length of output: 1574


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== VmExpungeMetadataFlow around line 83 ==='
cat -n compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java | sed -n '60,110p'

echo
echo '=== APIScanVmInstanceMetadataFromPrimaryStorageMsg implementation ==='
rg -n 'handle\(APIScanVmInstanceMetadataFromPrimaryStorageMsg' \
  storage -A30 | head -100

Repository: MatheMatrix/zstack

Length of output: 7216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== CleanupVmInstanceMetadataOnPrimaryStorageGC.java ==='
cat -n compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java

echo
echo '=== LocalStorageBase handler overrides (all methods) ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
  plugin/localstorage --type=java

echo
echo '=== SMPPrimaryStorageBase metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
  plugin/sharedMountPointPrimaryStorage --type=java

echo
echo '=== CephPrimaryStorageBase metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
  plugin/ceph --type=java

echo
echo '=== ExternalPrimaryStorage metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
  storage/src/main/java/org/zstack/storage/addon/primary --type=java

Repository: MatheMatrix/zstack

Length of output: 3265


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Check all backends for metadata handler overrides ==='
for pattern in 'UpdateVmInstanceMetadataOnPrimaryStorageMsg' 'GetVmInstanceMetadataFromPrimaryStorageMsg' 'ScanVmInstanceMetadataFromPrimaryStorageMsg' 'CleanupVmInstanceMetadataOnPrimaryStorageMsg'; do
  echo "=== Searching for $pattern ==="
  rg -n "handle\($pattern" --type=java plugin storage 2>/dev/null || echo "Not found in: $pattern"
done

echo
echo '=== Check VmExpungeMetadataFlow failure handling ==='
cat -n compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java | sed -n '100,115p'

Repository: MatheMatrix/zstack

Length of output: 7081


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Verify NfsPrimaryStorage is the only base handler implementer ==='
rg -n 'class.*extends PrimaryStorageBase' --type=java plugin storage | head -20

echo
echo '=== Check if other backends explicitly support metadata ==='
rg -n '@Bean.*VmMetadataPathBuildExtensionPoint|class.*implements.*VmMetadataPathBuildExtensionPoint' \
  --type=java plugin storage header 2>/dev/null | head -30

Repository: MatheMatrix/zstack

Length of output: 1649


CleanupVmInstanceMetadataOnPrimaryStorageGC 需要增加存储类型检查,防止向不支持的后端重复发送消息。

当前代码存在两层风险:

  1. VmExpungeMetadataFlow 虽然在初始发送时检查了 VmMetadataPathBuildExtensionPoint(仅 NFS 和 LocalStorage 实现),但失败时会提交 GC 任务重试(第 107 行)。

  2. CleanupVmInstanceMetadataOnPrimaryStorageGC 则跳过了所有能力检查,直接向任何存在的主存储无条件发送清理消息(第 41-48 行)。结果:

    • SMPPrimaryStorageBaseExternalPrimaryStorageCephPrimaryStorageBase 无对应实现 → 返回"operation not supported"
    • LocalStorageBase 虽有扩展点但基类未覆盖消息处理 → 也返回默认错误
    • NfsPrimaryStorage 完整实现所有 4 个处理器

这导致清理失败的存储类型会反复通过 GC 重试循环发送消息,产生持续的失败日志和重试开销。

建议 CleanupVmInstanceMetadataOnPrimaryStorageGC 在 triggerNow 方法中增加存储类型和扩展点的验证,模仿 VmExpungeMetadataFlow 的检查逻辑(第 65-71 行),或者确保所有使用此功能的存储后端都实现了对应的消息处理器。

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

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1790 - 1818, CleanupVmInstanceMetadataOnPrimaryStorageGC currently
sends CleanupVmInstanceMetadataOnPrimaryStorageMsg to every primary storage,
causing repeated "operation not supported" replies and GC retries; modify
CleanupVmInstanceMetadataOnPrimaryStorageGC.triggerNow to first filter target
primary storages by checking they are of a supported type and that a
VmMetadataPathBuildExtensionPoint implementation exists for them (reuse the same
check used in VmExpungeMetadataFlow), and only send
CleanupVmInstanceMetadataOnPrimaryStorageMsg to those primaries; alternatively,
skip scheduling GC for primaries that would fall back to PrimaryStorageBase
handlers (which return operr) so you avoid futile retries.

@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/ZSV-11559@@4 branch from dfbb104 to 1182c2c Compare March 26, 2026 05:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (3)
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)

1863-1869: ⚠️ Potential issue | 🟡 Minor

主存储被删除时 psType 为 null,错误消息将不准确。

findValue() 在 API 路由后、实际执行前可能返回 null(主存储被删除的竞态情况)。当前代码会将其误报为 "type null does not support metadata",且 getExtensionFromMap(null, ...) 的行为不可预期。

🩹 建议修改
         String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue();
+        if (psType == null) {
+            evt.setError(Platform.operr("primary storage[uuid:%s] not found", msg.getPrimaryStorageUuid()));
+            bus.publish(evt);
+            return;
+        }
         VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class);
         if (ext == null) {
             evt.setError(Platform.operr("primary storage type %s does not support metadata", psType));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1863 - 1869, The code calls Q.New(...).select(...).findValue() into
psType and then uses pluginRgty.getExtensionFromMap(psType,...), but psType can
be null if the primary storage was deleted; add a null check on psType (the
result of Q.New(...).select(...).findValue()) before calling
pluginRgty.getExtensionFromMap and return a clear not-found error (e.g. set
evt.setError with an operation error indicating the primary storage UUID was not
found or deleted) and publish evt, instead of proceeding to call
getExtensionFromMap with a null type or logging "type null does not support
metadata".
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)

89-93: ⚠️ Potential issue | 🔴 Critical

hostUuid 为空时不要继续下发 cleanup。

这里会把 null 直接塞进 CleanupVmInstanceMetadataOnPrimaryStorageMsg。对 LocalStorage 来说,下游只能靠 hostUuid 或 root volume ref 反解宿主机;而 VmExpungeMetadataFlowconf/springConfigXml/VmInstanceManager.xml:116-122 的 expunge 链里排在 root/memory/cache volume flow 之后,root volume ref 此时可能已经被前面的 flow 删掉了。结果就是首轮 cleanup 失败,随后提交的 GC 仍然带着同样的 null hostUuid,会一直重试但无法收敛。这里至少要在 hostUuid == null 时直接短路,或改成一个不依赖 host 的兜底路径。

🛠️ 一个最小修复方向
 String hostUuid = spec.getVmInventory().getHostUuid();
 if (hostUuid == null) {
     hostUuid = spec.getVmInventory().getLastHostUuid();
 }
+if (hostUuid == null) {
+    logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no hostUuid/lastHostUuid, skip metadata cleanup", vmUuid));
+    trigger.next();
+    return;
+}
 cmsg.setHostUuid(hostUuid);
🤖 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 89 - 93, The flow VmExpungeMetadataFlow currently sets hostUuid on
CleanupVmInstanceMetadataOnPrimaryStorageMsg using
spec.getVmInventory().getHostUuid() (falling back to getLastHostUuid()), which
can be null and causes cleanup to be repeatedly retried; change the logic in
VmExpungeMetadataFlow so that before calling cmsg.setHostUuid(...) you check if
hostUuid is null and short-circuit (skip sending the cleanup message) or use a
host-independent fallback path; specifically, locate the block that reads
hostUuid from spec.getVmInventory() and either return/complete the flow early
when hostUuid == null or construct an alternative cleanup flow that does not
rely on hostUuid instead of putting a null into cmsg.setHostUuid.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)

1991-1996: ⚠️ Potential issue | 🟠 Major

getConnectedHostForOperation() 的异常路径这里还没接住。

同文件 Line 461-470 已经按“可能直接抛 OperationFailureException”来处理了。这里 Line 1991、Line 2016、Line 2041、Line 2066 继续先拿列表再判空,会让 reply.setError(...) 分支在无可用主机时根本走不到。请像现有 handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg) 一样先 catch 再构造对应 reply。

🐛 建议修复模式
-        List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
-        if (connectedHosts.isEmpty()) {
+        List<HostInventory> connectedHosts;
+        try {
+            connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+        } catch (OperationFailureException e) {
+            reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
+            bus.reply(msg, reply);
+            return;
+        }
+        if (connectedHosts.isEmpty()) {
             reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
             bus.reply(msg, reply);
             return;
         }

Also applies to: 2016-2021, 2041-2046, 2066-2071

🤖 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 1991 - 1996, The calls to
factory.getConnectedHostForOperation(getSelfInventory()) in NfsPrimaryStorage
can throw an OperationFailureException and must be wrapped in a try/catch so the
code constructs and returns the appropriate reply on exception (same pattern
used in handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg)); for each
occurrence (the blocks that currently do List<HostInventory> connectedHosts =
factory.getConnectedHostForOperation(...); if (connectedHosts.isEmpty()) {
reply.setError(...); bus.reply(msg, reply); return; }) change to try {
connectedHosts = factory.getConnectedHostForOperation(...) } catch
(OperationFailureException e) {
reply.setError(errf.stringToOperationError(e.getMessage())); bus.reply(msg,
reply); return; } and keep the existing empty-list check afterwards so the reply
variable and bus.reply(msg, reply) are always used on error paths.
🧹 Nitpick comments (2)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)

80-89: 代码重复:建议复用 resolveBaseDir 方法。

此处的 baseDir 解析逻辑与 resolveBaseDir 方法(lines 29-38)几乎相同,唯一区别是错误处理方式不同(warn + 返回空结果 vs 抛出异常)。建议提取一个可复用的私有方法,或者直接调用 resolveBaseDir 并在外层捕获异常处理。

♻️ 建议重构
     `@Override`
     public PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths) {
         if (allOldPaths == null || allOldPaths.isEmpty()) {
             PathReplacementResult result = new PathReplacementResult();
             result.setMetadataToCurrentPathMap(Collections.emptyMap());
             return result;
         }

-        String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue();
-        if (baseDir == null) {
-            baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue();
-        }
-        if (baseDir == null) {
+        String baseDir;
+        try {
+            baseDir = resolveBaseDir(targetPsUuid);
+        } catch (CloudRuntimeException e) {
             logger.warn(String.format("NFS PS[uuid:%s] has no mountPath or url, path replacement disabled", targetPsUuid));
             PathReplacementResult result = new PathReplacementResult();
             result.setMetadataToCurrentPathMap(Collections.emptyMap());
             return result;
         }
-        String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/";
+        String newPrefix = baseDir + "/";
🤖 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/NfsVmMetadataExtension.java`
around lines 80 - 89, The baseDir resolution in NfsVmMetadataExtension (the
block that queries PrimaryStorageVO.select(mountPath/url) for targetPsUuid)
duplicates logic from the resolveBaseDir(...) method; refactor to reuse
resolveBaseDir by calling resolveBaseDir(targetPsUuid) and handle its exception
here: catch the exception thrown by resolveBaseDir and perform the current
warn+return empty PathReplacementResult behavior, or extract a private helper
used by both places that returns Optional<String>/null and let callers map to
their respective error handling; update references in the
PathReplacementResult-returning code path to use this shared helper instead of
duplicating the DB lookup.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)

2157-2160: hostUuid 在扫描结果里更像“scanner host”,不要和资源归属语义混用。

Line 2159 把每个 entry 的 hostUuid 都回填成当前扫描宿主机。NFS 目录是共享的,换一台 connected host 再扫,这个值就会跟着变;如果上层把它当成元数据条目的所属/来源 host,结果会不稳定。建议至少把语义文档化,或拆成单独字段。

🤖 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 2157 - 2160, The scan currently overwrites each metadata entry's
hostUuid via entry.setHostUuid(hostUuid) (in the
ScanVmInstanceMetadataFromPrimaryStorageReply population), which conflates the
scanning host with the metadata's owner/source; instead, stop mutating the
entry.hostUuid and add a separate scanner field (e.g. scannerHostUuid) on the
metadata entry or on ScanVmInstanceMetadataFromPrimaryStorageReply, populate
that with the current hostUuid, and document the semantics so callers can
distinguish the scanner host from the resource owner; update references to
rsp.metadataEntries, entry.setHostUuid(hostUuid), and
r.setVmInstanceMetadata(...) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java`:
- Around line 194-198: 在 Registering
状态的事务注册里(Registering.Registering.transactions / Transaction 调用序列),为探测/对账类兜底事件
VmInstanceStateEvent.unknown 和 VmInstanceStateEvent.noState 补上映射(指向现有的离线容错状态
VmInstanceState.Stopped,与已注册的 stopped/destroying/destroyed 一致),以避免 nextState()
在接收到 unknown/noState 时抛出 CloudRuntimeException;也请保持映射风格和其它 Transaction
条目一致以便行为统一。
- Line 34: The Registering enum constant in VmInstanceState is currently
constructed with a null drivenEvent which causes downstream NPEs when consumers
like VmInstanceBase.changeVmStateInDb(...) access .n without null checks; change
the Registering declaration to supply a valid non-null drivenEvent (use an
appropriate existing VmInstanceStateDrivenEvent value) so all state transitions
have a concrete event, or alternatively add null-safe handling in
changeVmStateInDb and other consumers to guard against a null drivenEvent;
prefer setting a non-null event on Registering in VmInstanceState to keep
semantics consistent.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3471-3474: The loop using While<>(connectedHostUuids).all
currently calls getHypervisorBackendFactoryByHostUuid(...) and
f.getHypervisorBackend(self) outside of any per-host try/catch, so if those
calls throw (e.g., host unbound) the completion (com) is never resolved; wrap
the per-host resolution and bkd.handle(...) invocation in a try/catch inside the
lambda (the body passed to While<>(...).all), catch any Throwable, call
com.addError(...) with context (hostUuid and exception) and then com.done() (or
otherwise mark that host as failed) so the loop continues to the next host;
reference getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend, and bkd.handle in your changes.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3923-3940: The
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid,
ReturnValueCompletion) method calls httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,
...) with hostUuid possibly null; add an early-null check for hostUuid at the
start of that method and short-circuit the logic (e.g., skip the httpCall and
either call completion.success(new
CleanupVmInstanceMetadataOnPrimaryStorageReply()) or trigger the GC/cleanup
scheduling path used for host-less local storage) so the method returns cleanly
when hostUuid is null; ensure the short-circuit uses the same completion
semantics as the success branch and log a debug/info message indicating hostUuid
was missing.
- Around line 3942-3949: handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)
currently forwards installPaths/oldPrefix/newPrefix to
PrefixRebaseBackingFilesCmd without validation; add a fail‑fast validation in
the handle method before constructing/sending PrefixRebaseBackingFilesCmd:
verify msg.getInstallPaths() is non‑null and non‑empty, each path is a safe
absolute/storage path and matches expected backing file pattern, and
oldPrefix/newPrefix are non‑null, non‑empty and do not contain path traversal or
illegal characters; on validation failure, complete via the provided completion
with an error (use ReturnValueCompletion.completeExceptionally or the existing
completion.fail pattern) rather than calling httpCall, so
PrefixRebaseBackingFilesCmd and httpCall are only used when inputs are
validated.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 92-97: The current extractOldPrefix logic
(VmInstanceMetadataConstants.extractOldPrefix) wrongly matches markers inside
the mount path; update it to perform a boundary-aware search (e.g., use
lastIndexOf(marker) and verify the match corresponds to the path segment
boundary, or change the signature to accept the mount root and only search after
that boundary) so it returns the correct prefix ending at the mount path (e.g.,
"/nfs/dataVolumes/backup/"); then ensure callers such as the loop that builds
oldPrefix and calculatePathReplacements use the revised method/signature so
replacements are computed from the correct oldPrefix.

---

Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-93: The flow VmExpungeMetadataFlow currently sets hostUuid on
CleanupVmInstanceMetadataOnPrimaryStorageMsg using
spec.getVmInventory().getHostUuid() (falling back to getLastHostUuid()), which
can be null and causes cleanup to be repeatedly retried; change the logic in
VmExpungeMetadataFlow so that before calling cmsg.setHostUuid(...) you check if
hostUuid is null and short-circuit (skip sending the cleanup message) or use a
host-independent fallback path; specifically, locate the block that reads
hostUuid from spec.getVmInventory() and either return/complete the flow early
when hostUuid == null or construct an alternative cleanup flow that does not
rely on hostUuid instead of putting a null into cmsg.setHostUuid.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1991-1996: The calls to
factory.getConnectedHostForOperation(getSelfInventory()) in NfsPrimaryStorage
can throw an OperationFailureException and must be wrapped in a try/catch so the
code constructs and returns the appropriate reply on exception (same pattern
used in handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg)); for each
occurrence (the blocks that currently do List<HostInventory> connectedHosts =
factory.getConnectedHostForOperation(...); if (connectedHosts.isEmpty()) {
reply.setError(...); bus.reply(msg, reply); return; }) change to try {
connectedHosts = factory.getConnectedHostForOperation(...) } catch
(OperationFailureException e) {
reply.setError(errf.stringToOperationError(e.getMessage())); bus.reply(msg,
reply); return; } and keep the existing empty-list check afterwards so the reply
variable and bus.reply(msg, reply) are always used on error paths.

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1869: The code calls Q.New(...).select(...).findValue() into
psType and then uses pluginRgty.getExtensionFromMap(psType,...), but psType can
be null if the primary storage was deleted; add a null check on psType (the
result of Q.New(...).select(...).findValue()) before calling
pluginRgty.getExtensionFromMap and return a clear not-found error (e.g. set
evt.setError with an operation error indicating the primary storage UUID was not
found or deleted) and publish evt, instead of proceeding to call
getExtensionFromMap with a null type or logging "type null does not support
metadata".

---

Nitpick comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2157-2160: The scan currently overwrites each metadata entry's
hostUuid via entry.setHostUuid(hostUuid) (in the
ScanVmInstanceMetadataFromPrimaryStorageReply population), which conflates the
scanning host with the metadata's owner/source; instead, stop mutating the
entry.hostUuid and add a separate scanner field (e.g. scannerHostUuid) on the
metadata entry or on ScanVmInstanceMetadataFromPrimaryStorageReply, populate
that with the current hostUuid, and document the semantics so callers can
distinguish the scanner host from the resource owner; update references to
rsp.metadataEntries, entry.setHostUuid(hostUuid), and
r.setVmInstanceMetadata(...) accordingly.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 80-89: The baseDir resolution in NfsVmMetadataExtension (the block
that queries PrimaryStorageVO.select(mountPath/url) for targetPsUuid) duplicates
logic from the resolveBaseDir(...) method; refactor to reuse resolveBaseDir by
calling resolveBaseDir(targetPsUuid) and handle its exception here: catch the
exception thrown by resolveBaseDir and perform the current warn+return empty
PathReplacementResult behavior, or extract a private helper used by both places
that returns Optional<String>/null and let callers map to their respective error
handling; update references in the PathReplacementResult-returning code path to
use this shared helper instead of duplicating the DB lookup.
🪄 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: ef2262b9-d6b9-4ac1-80e3-0df96d884ac4

📥 Commits

Reviewing files that changed from the base of the PR and between dfbb104 and 1182c2c.

⛔ 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 (148)
  • 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/upgrade/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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • 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/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/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/APIDeleteVmNicMsg.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/APIExpungeVmInstanceMsg.java
  • 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/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/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/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • 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/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.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
✅ Files skipped from review due to trivial changes (89)
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
  • header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • 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/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.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/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/APIDeleteTagMsg.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/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/APIDeleteVmNicMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
  • 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/APISetVmBootOrderMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.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/APISetVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • 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/UpdateVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.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/VolumeResourceMetadata.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.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/APIExpungeDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
  • sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
  • header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
  • testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
  • testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
  • header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
  • conf/db/upgrade/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
  • sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
  • sdk/src/main/java/SourceClassMap.java
🚧 Files skipped from review as they are similar to previous changes (42)
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
  • header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.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/tag/APICreateSystemTagsMsg.java
  • header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
  • header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
  • compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
  • sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java

Unknown(VmInstanceStateEvent.unknown),
Crashed(VmInstanceStateEvent.crashed);
Crashed(VmInstanceStateEvent.crashed),
Registering(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 | 🔴 Critical

避免为新状态使用空驱动事件(会触发下游 NPE)

Line 34 把 RegisteringdrivenEvent 设为 null。结合 compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java:1,50 中对状态驱动事件的无空保护访问(changeVmStateInDb(... .n)),这里会在注册态流转/落库时触发空指针并中断流程。建议为 Registering 提供非空事件,或在所有消费点统一做空值保护。

🤖 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/VmInstanceState.java` at line 34,
The Registering enum constant in VmInstanceState is currently constructed with a
null drivenEvent which causes downstream NPEs when consumers like
VmInstanceBase.changeVmStateInDb(...) access .n without null checks; change the
Registering declaration to supply a valid non-null drivenEvent (use an
appropriate existing VmInstanceStateDrivenEvent value) so all state transitions
have a concrete event, or alternatively add null-safe handling in
changeVmStateInDb and other consumers to guard against a null drivenEvent;
prefer setting a non-null event on Registering in VmInstanceState to keep
semantics consistent.

Comment thread header/src/main/java/org/zstack/header/vm/VmInstanceState.java
Comment on lines +3471 to +3474
new While<>(connectedHostUuids).all((hostUuid, com) -> {
LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {

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

把 backend 解析也纳入逐主机的失败分支。

这里先拿到 connectedHostUuids,随后又在 Line 3472-3473 重新按 hostUuid 查 factory/backend。主机如果在这两个步骤之间被解绑或删除,getHypervisorBackendFactoryByHostUuid() 会在 bkd.handle() 之前直接抛异常,当前分支既不会 com.addError() 也不会 com.done(),一次主机抖动就可能把本来设计成“部分失败继续”的扫描整体打断。建议把 factory/backend 解析包进 try/catch,按单主机失败记账后继续扫描。

🛠️ 一个最小修复方向
 new While<>(connectedHostUuids).all((hostUuid, com) -> {
-    LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
-    LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+    final LocalStorageHypervisorBackend bkd;
+    try {
+        LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+        bkd = f.getHypervisorBackend(self);
+    } catch (Exception e) {
+        com.addError(operr("failed to prepare vm metadata scan on host[uuid:%s]: %s", hostUuid, e.getMessage()));
+        com.done();
+        return;
+    }
     bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {
🤖 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/LocalStorageBase.java`
around lines 3471 - 3474, The loop using While<>(connectedHostUuids).all
currently calls getHypervisorBackendFactoryByHostUuid(...) and
f.getHypervisorBackend(self) outside of any per-host try/catch, so if those
calls throw (e.g., host unbound) the completion (com) is never resolved; wrap
the per-host resolution and bkd.handle(...) invocation in a try/catch inside the
lambda (the body passed to While<>(...).all), catch any Throwable, call
com.addError(...) with context (hostUuid and exception) and then com.done() (or
otherwise mark that host as failed) so the loop continues to the next host;
reference getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend, and bkd.handle in your changes.

Comment on lines +3923 to +3940
@Override
void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.metadataPath = msg.getMetadataPath();

httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) {
@Override
public void success(CleanupVmMetadataRsp rsp) {
CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
completion.success(reply);
}

@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}

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

缺少 hostUuid 空值保护会导致清理链路失败

Line 3928 直接用 hostUuid 发起 httpCall。在本地存储场景中,hostUuid 为空时该调用会失败,导致元数据清理无法完成。建议在入口先做空值短路(跳过或转 GC)。

建议修复
 `@Override`
 void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+    if (StringUtils.isBlank(hostUuid)) {
+        logger.warn(String.format("skip cleanup vm metadata on local storage[uuid:%s], metadataPath:%s because hostUuid is null/blank",
+                self.getUuid(), msg.getMetadataPath()));
+        completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply());
+        return;
+    }
+
     CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
     cmd.metadataPath = msg.getMetadataPath();

As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束".

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

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3923 - 3940, The
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid,
ReturnValueCompletion) method calls httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,
...) with hostUuid possibly null; add an early-null check for hostUuid at the
start of that method and short-circuit the logic (e.g., skip the httpCall and
either call completion.success(new
CleanupVmInstanceMetadataOnPrimaryStorageReply()) or trigger the GC/cleanup
scheduling path used for host-less local storage) so the method returns cleanly
when hostUuid is null; ensure the short-circuit uses the same completion
semantics as the success branch and log a debug/info message indicating hostUuid
was missing.

Comment on lines +3942 to +3949
@Override
void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<RebaseVolumeBackingFileOnPrimaryStorageReply> completion) {
PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd();
cmd.filePaths = msg.getInstallPaths();
cmd.oldPrefix = msg.getOldPrefix();
cmd.newPrefix = msg.getNewPrefix();

httpCall(PREFIX_REBASE_BACKING_FILES_PATH, hostUuid, cmd, PrefixRebaseBackingFilesRsp.class, new ReturnValueCompletion<PrefixRebaseBackingFilesRsp>(completion) {

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

前缀重写操作缺少入参校验,存在误改 backing chain 风险

当前直接透传 installPaths/oldPrefix/newPrefix 到 agent。若前缀或路径输入异常,会把错误映射下发,风险较高。建议在本地先做 fail-fast 校验。

建议修复
 `@Override`
 void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<RebaseVolumeBackingFileOnPrimaryStorageReply> completion) {
+    if (CollectionUtils.isEmpty(msg.getInstallPaths())
+            || StringUtils.isBlank(msg.getOldPrefix())
+            || StringUtils.isBlank(msg.getNewPrefix())) {
+        completion.fail(operr("invalid rebase request: installPaths/oldPrefix/newPrefix must not be empty"));
+        return;
+    }
+
+    Optional<String> illegalPath = msg.getInstallPaths().stream()
+            .filter(p -> p == null || !p.startsWith(msg.getOldPrefix()))
+            .findFirst();
+    if (illegalPath.isPresent()) {
+        completion.fail(operr("invalid rebase request: installPath[%s] does not start with oldPrefix[%s]",
+                illegalPath.get(), msg.getOldPrefix()));
+        return;
+    }
+
     PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd();
     cmd.filePaths = msg.getInstallPaths();
     cmd.oldPrefix = msg.getOldPrefix();

As per coding guidelines "条件判断中的处理逻辑提前结束或抽成独立方法".

🤖 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 3942 - 3949, handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)
currently forwards installPaths/oldPrefix/newPrefix to
PrefixRebaseBackingFilesCmd without validation; add a fail‑fast validation in
the handle method before constructing/sending PrefixRebaseBackingFilesCmd:
verify msg.getInstallPaths() is non‑null and non‑empty, each path is a safe
absolute/storage path and matches expected backing file pattern, and
oldPrefix/newPrefix are non‑null, non‑empty and do not contain path traversal or
illegal characters; on validation failure, complete via the provided completion
with an error (use ReturnValueCompletion.completeExceptionally or the existing
completion.fail pattern) rather than calling httpCall, so
PrefixRebaseBackingFilesCmd and httpCall are only used when inputs are
validated.

Comment on lines +92 to +97
// Extract old prefix from the first recognizable path
String oldPrefix = null;
for (String path : allOldPaths) {
oldPrefix = VmInstanceMetadataConstants.extractOldPrefix(path);
if (oldPrefix != null) break;
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 查看 extractOldPrefix 的实现逻辑
ast-grep --pattern 'public static String extractOldPrefix($$$) {
  $$$
}'

Repository: MatheMatrix/zstack

Length of output: 1477


🏁 Script executed:

#!/bin/bash
# Find the STORAGE_PATH_MARKERS definition in VmInstanceMetadataConstants
rg -A 10 'STORAGE_PATH_MARKERS' header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java

Repository: MatheMatrix/zstack

Length of output: 580


🏁 Script executed:

#!/bin/bash
# View the full calculatePathReplacements method for context
sed -n '72,116p' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java

Repository: MatheMatrix/zstack

Length of output: 2199


extractOldPrefix 使用简单的字符串匹配,当挂载路径本身包含标记时可能导致错误的前缀提取。

VmInstanceMetadataConstants.extractOldPrefix() 使用 path.indexOf(marker) 查找硬编码的路径标记(/rootVolumes//dataVolumes//volumeSnapshots//memory/),并返回第一个匹配位置之前的子串。如果挂载路径本身包含这些标记,例如挂载路径为 /nfs/dataVolumes/backup 而旧路径为 /nfs/dataVolumes/backup/dataVolumes/vm1/vol.qcow2,则 indexOf("/dataVolumes/") 会匹配挂载路径部分(位置 5),导致返回 / 而非正确的 /nfs/dataVolumes/backup/。这会导致 calculatePathReplacements 中的路径映射产生错误的 old→new 转换,破坏卷的 backing file 链。

建议:改进匹配策略,例如从挂载路径末尾开始扫描,或显式传递挂载路径边界信息。

🤖 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/NfsVmMetadataExtension.java`
around lines 92 - 97, The current extractOldPrefix logic
(VmInstanceMetadataConstants.extractOldPrefix) wrongly matches markers inside
the mount path; update it to perform a boundary-aware search (e.g., use
lastIndexOf(marker) and verify the match corresponds to the path segment
boundary, or change the signature to accept the mount root and only search after
that boundary) so it returns the correct prefix ending at the mount path (e.g.,
"/nfs/dataVolumes/backup/"); then ensure callers such as the loop that builds
oldPrefix and calculatePathReplacements use the revised method/signature so
replacements are computed from the correct oldPrefix.

@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/ZSV-11559@@4 branch from 1182c2c to 295ef96 Compare March 26, 2026 06:00
APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373
@MatheMatrix MatheMatrix force-pushed the sync/tao.gan/ZSV-11559@@4 branch from 295ef96 to 520245e Compare March 26, 2026 06:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

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

89-93: ⚠️ Potential issue | 🟠 Major

不要把 null hostUuid 继续下发到主存清理消息。

从未启动过的 VM 上,hostUuidlastHostUuid 都可能为空;Line 93 仍然把它塞进 cmsg。而这个 flow 又是在根卷 expunge 之后执行,LocalStorage 一侧通常已经无法再靠 rootVolumeUuid 反查 host,结果是清理/GC 很容易一直失败。

🛠️ 一个更安全的短路方式
 String hostUuid = spec.getVmInventory().getHostUuid();
 if (hostUuid == null) {
     hostUuid = spec.getVmInventory().getLastHostUuid();
 }
+if (hostUuid == null) {
+    logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no hostUuid/lastHostUuid, skip metadata cleanup", vmUuid));
+    trigger.next();
+    return;
+}
 cmsg.setHostUuid(hostUuid);

如果你更想走 GC 重试,这里还需要同步保证 GC 不再依赖同一条 host 定位链路;否则只是把失败延后。

🤖 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 89 - 93, 在 VmExpungeMetadataFlow 中不要把可能为 null 的 hostUuid 继续写入
cmsg:在使用 spec.getVmInventory().getHostUuid() / getLastHostUuid() 后,如果两者都为 null
就不要调用 cmsg.setHostUuid(...)(改为跳过设置并可选地记录一条 debug 日志),否则在根卷已被 expunge 的场景下
LocalStorage 无法通过 rootVolumeUuid 反查 host 会导致后续清理/GC
一直失败;定位到相关符号:VmExpungeMetadataFlow、spec.getVmInventory(), getHostUuid(),
getLastHostUuid(), cmsg.setHostUuid() 并按此修改。
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (1)

3471-3474: ⚠️ Potential issue | 🟠 Major

把 backend 解析也纳入单主机失败分支。

Line 3472-3473 如果 host 在拿到 connectedHostUuids 后被解绑或删除,这里会在 bkd.handle() 前直接抛异常,com 既不会 addError() 也不会 done(),整轮扫描可能被中断。

🛠️ 最小修复方向
 new While<>(connectedHostUuids).all((hostUuid, com) -> {
-    LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
-    LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+    final LocalStorageHypervisorBackend bkd;
+    try {
+        LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+        bkd = f.getHypervisorBackend(self);
+    } catch (Exception e) {
+        com.addError(operr("failed to prepare vm metadata scan on host[uuid:%s] on local primary storage[uuid:%s]: %s",
+                hostUuid, self.getUuid(), e.getMessage()));
+        com.done();
+        return;
+    }
     bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {
🤖 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/LocalStorageBase.java`
around lines 3471 - 3474, When iterating connectedHostUuids inside the While,
resolve the hypervisor factory/backend and call bkd.handle inside a try/catch
and treat null factory/backend as an error so the per-host Completion (com) is
always resolved; specifically, in the block using
getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend and bkd.handle with
ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>, catch any
exception (or null returns) when obtaining the factory/backend or before
invoking bkd.handle and call com.addError(...) and com.done() so a failure to
resolve the backend for a single host does not leave com unresolved and block
the overall scan.
🧹 Nitpick comments (3)
header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java (1)

31-31: 建议将 resolver 裸字符串收敛为常量

resolver = "volumeUuidToVmUuid" 硬编码在多个 API 消息类中(至少 13 处),存在字符串漂移风险。建议定义一个常量类并在框架初始化时对 resolver Bean 可用性进行单次校验,以提升代码维护性。

示例改法
-@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "volumeUuidToVmUuid", field = "rootVolumeUuid", updateOnFailure = true)
+@MetadataImpact(
+        value = MetadataImpact.Impact.STORAGE,
+        resolver = MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID,
+        field = "rootVolumeUuid",
+        updateOnFailure = true
+)
// 新增:header/src/main/java/org/zstack/header/vm/metadata/MetadataImpactResolvers.java
public final class MetadataImpactResolvers {
    private MetadataImpactResolvers() {}
    public static final String VOLUME_UUID_TO_VM_UUID = "volumeUuidToVmUuid";
}
🤖 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/volume/APICreateVolumeSnapshotGroupMsg.java`
at line 31, The resolver name "volumeUuidToVmUuid" is hard-coded in the
`@MetadataImpact` annotation (e.g., on APICreateVolumeSnapshotGroupMsg) and should
be centralized as a constant; add a final utility class (e.g.,
MetadataImpactResolvers with VOLUME_UUID_TO_VM_UUID) and replace all literal
occurrences of "volumeUuidToVmUuid" in annotations (including
APICreateVolumeSnapshotGroupMsg) with the constant; additionally add a one-time
startup validation in framework initialization to assert the resolver bean named
by MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID is present (fail fast or log a
clear error) so resolver availability is checked once at boot rather than
relying on scattered string literals.
header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java (1)

20-20: 建议在实现 MetadataImpact 处理器时避免直接使用 resolver 魔法字符串

本 PR 新增 64 个 @MetadataImpact 注解,涉及 7 种不同的 resolver 名称(如 vmUuidDirectResolvervolumeUuidToVmUuid 等),全部以字符串字面量形式指定。目前未发现对应的 Bean 实现或处理器,但一旦处理器启动时加载这些 resolver,拼写错误或名称不匹配将导致运行期 NoSuchBeanDefinitionException

建议在实现处理器时:

  • 使用常量类统一管理所有 resolver 名称(如 MetadataResolverNames.VM_UUID_DIRECT 而非硬编码字符串)
  • 在处理器初始化或应用启动时进行 resolver 存在性校验,快速发现配置错误
🤖 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/APIDetachIsoFromVmInstanceMsg.java`
at line 20, The MetadataImpact annotations use literal resolver names (e.g.,
vmUuidDirectResolver in `@MetadataImpact` on APIDetachIsoFromVmInstanceMsg), which
risks runtime NoSuchBeanDefinitionException; replace string literals with
centralized constants (create a MetadataResolverNames class with constants like
VM_UUID_DIRECT, VOLUME_UUID_TO_VM_UUID, etc.) and update all `@MetadataImpact`
usages to reference those constants; additionally, add an initialization-time
validation in the MetadataImpact processor (the class handling MetadataImpact
annotations) to iterate required resolver names and assert the corresponding
resolver beans exist, failing fast with a clear error if any are missing.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)

80-89: 考虑复用 resolveBaseDir 逻辑

Lines 80-89 的查询逻辑与 resolveBaseDir() 方法重复。虽然行为不同(此处返回空结果而非抛异常),但可以考虑提取一个带可选异常处理的私有方法来减少重复:

可选重构
+    private String resolveBaseDirOrNull(String primaryStorageUuid) {
+        String baseDir = Q.New(PrimaryStorageVO.class)
+                .select(PrimaryStorageVO_.mountPath)
+                .eq(PrimaryStorageVO_.uuid, primaryStorageUuid)
+                .findValue();
+        if (baseDir == null) {
+            baseDir = Q.New(PrimaryStorageVO.class)
+                    .select(PrimaryStorageVO_.url)
+                    .eq(PrimaryStorageVO_.uuid, primaryStorageUuid)
+                    .findValue();
+        }
+        return baseDir;
+    }
+
     private String resolveBaseDir(String primaryStorageUuid) {
-        String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
-        if (baseDir == null) {
-            baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue();
-        }
+        String baseDir = resolveBaseDirOrNull(primaryStorageUuid);
         if (baseDir == null) {
             throw new CloudRuntimeException(...);
         }
         return baseDir.endsWith("/") ? baseDir.substring(0, baseDir.length() - 1) : baseDir;
     }
🤖 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/NfsVmMetadataExtension.java`
around lines 80 - 89, The mountPath/url lookup in NfsVmMetadataExtension (the
block that queries PrimaryStorageVO for mountPath or url using targetPsUuid and
returns an empty PathReplacementResult) duplicates resolveBaseDir(); extract a
single private helper (e.g., resolveBaseDirForUuid(String psUuid, boolean
throwIfMissing) or resolveBaseDirOptional) that performs the two-step Q.New(...)
select logic and returns the baseDir or null/throws based on a flag, then
replace this duplicated block to call that helper and keep behavior here (return
empty PathReplacementResult when null) while resolveBaseDir() can call the same
helper with throwIfMissing=true to preserve existing exception behavior.
🤖 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 41-60: 清晰一句话:当 hostUuid 为 null 时当前 GC 会持续发送必败的
CleanupVmInstanceMetadataOnPrimaryStorageMsg 导致不可自愈;修复方法是在发送前在 submitGC(或创建
CleanupVmInstanceMetadataOnPrimaryStorageMsg 的代码路径)检查 hostUuid,若为 null 则用
rootVolumeUuid 反查宿主机(通过查询卷/卷挂载/volume VO 得到宿主机 UUID);如果能查到则填充
msg.setHostUuid(foundHostUuid) 并继续 bus.send,否则不要发送消息并直接调用 completion.success()(或
cancel/成功结束 GC),以避免持久化一个必败请求。

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 908-935: Add the missing import statements for the VM
metadata-related classes referenced in this file so it compiles: import the
GetVmInstanceMetadataFromPrimaryStorage* classes,
ScanVmInstanceMetadataFromPrimaryStorage* classes,
CleanupVmInstanceMetadataOnPrimaryStorage* classes, and the VmMetadataScanEntry
type (the same symbols used alongside UpdateVmInstanceMetadataOnPrimaryStorage*
and referenced in
WriteVmMetadataCmd/ScanVmMetadataRsp/GetVmInstanceMetadataRsp). Place these
imports with the other org.zstack storage/primary-related imports so the
compiler can resolve those types.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 337-372: The simulator is missing a RequestMapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH; add a new controller
method annotated with
`@RequestMapping`(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH,
method=RequestMethod.POST) that deserializes the request body into the
appropriate command class (e.g., RebaseBackingFilesCmd), appends it to the
simulator config's collection (e.g., config.rebaseBackingFilesCmds), sends a
reply via reply(entity, new RebaseBackingFilesRsp()) and returns null so tests
invoking handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) get a handled
response. Ensure the method signature and names match existing patterns used by
writeVmMetadata/getVmInstanceMetadata/scanVmMetadata/cleanupVmMetadata.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 134-145: createResourceRef currently unconditionally calls
dbf.persist on a new LocalStorageResourceRefVO which can cause a primary-key
conflict for the composite key (primaryStorageUuid, resourceUuid); modify
createResourceRef to first query the DB for an existing
LocalStorageResourceRefVO by primaryStorageUuid and resourceUuid (e.g. via
dbf.findByExample or a specific query), and if a record exists update its fields
(size, hostUuid, lastOpDate, etc.) and call dbf.update, otherwise persist the
new ref with dbf.persist; keep the method signature and ensure you use
LocalStorageResourceRefVO and dbf to locate and save/update the record.
- Around line 74-77: The current hard-coded marker matching in
VmInstanceMetadataConstants.extractOldPrefix() can mis-identify prefixes if
those marker substrings appear earlier in a mount path; update
extractOldPrefix() to locate markers from the path end (use lastIndexOf or scan
segments from the tail) or, preferably, resolve the real primary storage mount
point via primary storage metadata and compute the prefix boundary using that
exact mount path; ensure calculatePathReplacements() continues to rely on the
corrected extractOldPrefix() behavior and that callers such as
LocalStorageVmMetadataExtension (where oldPrefix is used) and
RebaseVolumeBackingFileOnPrimaryStorageMsg receive the accurate replacements to
avoid corrupting backing-file chains.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java`:
- Around line 990-994: The PrefixRebaseBackingFilesCmd currently only carries
filePaths, oldPrefix and newPrefix which allows an upstream prefix mis-detection
to be blindly applied; add source and target primary-storage mount path fields
(e.g., srcMountPath and destMountPath) to PrefixRebaseBackingFilesCmd and update
the agent handler that performs the rebasing to validate that each entry in
filePaths actually starts with oldPrefix and that the provided
srcMountPath/destMountPath match the agent’s mounted PS path before making any
changes; if any filePath fails the prefix check or mount-path mismatch, reject
the command with an error instead of applying the rewrite to backing files.

In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-618: The test harness is missing a simulator endpoint for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH used by
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg,...), so add a simulator(...)
block in LocalStorageSpec.groovy that registers PREFIX_REBASE_BACKING_FILES_PATH
and returns the matching response object (e.g. new
LocalStorageKvmBackend.RebaseVolumeBackingFileOnPrimaryStorageRsp()) so tests
exercising backing-file prefix rebases hit the mock agent.

---

Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-93: 在 VmExpungeMetadataFlow 中不要把可能为 null 的 hostUuid 继续写入
cmsg:在使用 spec.getVmInventory().getHostUuid() / getLastHostUuid() 后,如果两者都为 null
就不要调用 cmsg.setHostUuid(...)(改为跳过设置并可选地记录一条 debug 日志),否则在根卷已被 expunge 的场景下
LocalStorage 无法通过 rootVolumeUuid 反查 host 会导致后续清理/GC
一直失败;定位到相关符号:VmExpungeMetadataFlow、spec.getVmInventory(), getHostUuid(),
getLastHostUuid(), cmsg.setHostUuid() 并按此修改。

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3471-3474: When iterating connectedHostUuids inside the While,
resolve the hypervisor factory/backend and call bkd.handle inside a try/catch
and treat null factory/backend as an error so the per-host Completion (com) is
always resolved; specifically, in the block using
getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend and bkd.handle with
ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>, catch any
exception (or null returns) when obtaining the factory/backend or before
invoking bkd.handle and call com.addError(...) and com.done() so a failure to
resolve the backend for a single host does not leave com unresolved and block
the overall scan.

---

Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java`:
- Line 20: The MetadataImpact annotations use literal resolver names (e.g.,
vmUuidDirectResolver in `@MetadataImpact` on APIDetachIsoFromVmInstanceMsg), which
risks runtime NoSuchBeanDefinitionException; replace string literals with
centralized constants (create a MetadataResolverNames class with constants like
VM_UUID_DIRECT, VOLUME_UUID_TO_VM_UUID, etc.) and update all `@MetadataImpact`
usages to reference those constants; additionally, add an initialization-time
validation in the MetadataImpact processor (the class handling MetadataImpact
annotations) to iterate required resolver names and assert the corresponding
resolver beans exist, failing fast with a clear error if any are missing.

In
`@header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java`:
- Line 31: The resolver name "volumeUuidToVmUuid" is hard-coded in the
`@MetadataImpact` annotation (e.g., on APICreateVolumeSnapshotGroupMsg) and should
be centralized as a constant; add a final utility class (e.g.,
MetadataImpactResolvers with VOLUME_UUID_TO_VM_UUID) and replace all literal
occurrences of "volumeUuidToVmUuid" in annotations (including
APICreateVolumeSnapshotGroupMsg) with the constant; additionally add a one-time
startup validation in framework initialization to assert the resolver bean named
by MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID is present (fail fast or log a
clear error) so resolver availability is checked once at boot rather than
relying on scattered string literals.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 80-89: The mountPath/url lookup in NfsVmMetadataExtension (the
block that queries PrimaryStorageVO for mountPath or url using targetPsUuid and
returns an empty PathReplacementResult) duplicates resolveBaseDir(); extract a
single private helper (e.g., resolveBaseDirForUuid(String psUuid, boolean
throwIfMissing) or resolveBaseDirOptional) that performs the two-step Q.New(...)
select logic and returns the baseDir or null/throws based on a flag, then
replace this duplicated block to call that helper and keep behavior here (return
empty PathReplacementResult when null) while resolveBaseDir() can call the same
helper with throwIfMissing=true to preserve existing exception behavior.
🪄 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: 8364e772-ec3c-4833-a22b-5b53923d787c

📥 Commits

Reviewing files that changed from the base of the PR and between 1182c2c and 295ef96.

⛔ 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 (148)
  • 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/upgrade/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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • 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/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/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/APIDeleteVmNicMsg.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/APIExpungeVmInstanceMsg.java
  • 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/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/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/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • 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/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.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
✅ Files skipped from review due to trivial changes (97)
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
  • header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
  • header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
  • header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
  • sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
  • header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
  • header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
  • header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
  • header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
  • header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
  • header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
  • sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • conf/db/upgrade/V5.0.0__schema.sql
  • storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
🚧 Files skipped from review as they are similar to previous changes (22)
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
  • header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
  • testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
  • sdk/src/main/java/SourceClassMap.java

Comment on lines +41 to +60
CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg();
msg.setPrimaryStorageUuid(primaryStorageUuid);
msg.setVmUuid(vmUuid);
msg.setRootVolumeUuid(rootVolumeUuid);
msg.setMetadataPath(metadataPath);
msg.setHostUuid(hostUuid);

bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid);
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
logger.info(String.format("[MetadataCleanupGC] successfully cleaned up metadata " +
"for vm[uuid:%s] on ps[uuid:%s]", vmUuid, primaryStorageUuid));
completion.success();
} else {
logger.warn(String.format("[MetadataCleanupGC] failed to clean up metadata " +
"for vm[uuid:%s] on ps[uuid:%s]: %s", vmUuid, primaryStorageUuid, reply.getError()));
completion.fail(reply.getError());
}

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

hostUuid 为空时,这个 GC 会稳定重试失败。

VmExpungeMetadataFlow.submitGC() 会把 hostUuid 原样落到 GC;对于从未启动就删除的 VM,这里又把 null 继续塞进 CleanupVmInstanceMetadataOnPrimaryStorageMsg。本地存储后端随后按 hostUuid 路由 KVM 调用,结果只会反复失败,GC 无法自愈。建议在发送前先用 rootVolumeUuid 反查宿主机,查不到再 cancel()/success(),不要持久化一个必败请求。

🤖 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 41 - 60, 清晰一句话:当 hostUuid 为 null 时当前 GC 会持续发送必败的
CleanupVmInstanceMetadataOnPrimaryStorageMsg 导致不可自愈;修复方法是在发送前在 submitGC(或创建
CleanupVmInstanceMetadataOnPrimaryStorageMsg 的代码路径)检查 hostUuid,若为 null 则用
rootVolumeUuid 反查宿主机(通过查询卷/卷挂载/volume VO 得到宿主机 UUID);如果能查到则填充
msg.setHostUuid(foundHostUuid) 并继续 bus.send,否则不要发送消息并直接调用 completion.success()(或
cancel/成功结束 GC),以避免持久化一个必败请求。

Comment on lines +908 to +935
public static class WriteVmMetadataCmd extends AgentCommand {
public String metadata;
public String metadataPath;
public String vmUuid;
public String vmName;
public String vmCategory;
public String architecture;
public String schemaVersion;
}

public static class WriteVmMetadataRsp extends AgentResponse {
}

public static class GetVmInstanceMetadataCmd extends AgentCommand {
public String metadataPath;
}

public static class GetVmInstanceMetadataRsp extends AgentResponse {
public String metadata;
}

public static class ScanVmMetadataCmd extends AgentCommand {
public String metadataDir;
}

public static class ScanVmMetadataRsp extends AgentResponse {
public List<VmMetadataScanEntry> metadataEntries = new ArrayList<>();
}

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:

# 先查看文件的导入部分
head -100 plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java | grep -E "^import"

Repository: MatheMatrix/zstack

Length of output: 3472


🏁 Script executed:

# 搜索VmMetadataScanEntry类定义
fd -e java | xargs grep -l "class VmMetadataScanEntry"

Repository: MatheMatrix/zstack

Length of output: 201


🏁 Script executed:

# 搜索GetVmInstanceMetadataFromPrimaryStorageMsg等消息类
fd -e java | xargs grep -l "class GetVmInstanceMetadataFromPrimaryStorageMsg\|class ScanVmInstanceMetadataFromPrimaryStorageMsg\|class CleanupVmInstanceMetadataOnPrimaryStorageMsg"

Repository: MatheMatrix/zstack

Length of output: 375


🏁 Script executed:

# 检查LocalStorageKvmBackend.java在908-935和3878-3940行的内容
sed -n '908,935p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java

Repository: MatheMatrix/zstack

Length of output: 927


🏁 Script executed:

# 检查3878-3940行
sed -n '3878,3940p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java

Repository: MatheMatrix/zstack

Length of output: 3004


🏁 Script executed:

# 检查VmMetadataScanEntry的实际包名
head -20 header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java | grep "^package"

Repository: MatheMatrix/zstack

Length of output: 104


🏁 Script executed:

# 检查GetVmInstanceMetadataFromPrimaryStorageMsg等类的实际包名
head -20 header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java | grep "^package"

Repository: MatheMatrix/zstack

Length of output: 104


🏁 Script executed:

# 查找CleanupVmInstanceMetadataOnPrimaryStorageReply的位置
fd -e java | xargs grep -l "class CleanupVmInstanceMetadataOnPrimaryStorageReply"

Repository: MatheMatrix/zstack

Length of output: 170


🏁 Script executed:

# 查找ScanVmInstanceMetadataFromPrimaryStorageReply的位置
fd -e java | xargs grep -l "class ScanVmInstanceMetadataFromPrimaryStorageReply"

Repository: MatheMatrix/zstack

Length of output: 169


🏁 Script executed:

# 验证CleanupVmInstanceMetadataOnPrimaryStorageReply和ScanVmInstanceMetadataFromPrimaryStorageReply的包名
head -20 header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java | grep "^package"
head -20 header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java | grep "^package"

Repository: MatheMatrix/zstack

Length of output: 147


🏁 Script executed:

# 检查是否还有其他Reply类也需要导入
fd -e java | xargs grep -l "class GetVmInstanceMetadataFromPrimaryStorageReply"

Repository: MatheMatrix/zstack

Length of output: 168


🏁 Script executed:

# 最后验证一下LocalStorageKvmBackend.java中是否已有这些导入
grep -E "GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|VmMetadataScanEntry" plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java | head -20

Repository: MatheMatrix/zstack

Length of output: 634


补齐 vm metadata 相关导入,否则本文件编译不过。

这里新增代码除了 UpdateVmInstanceMetadataOnPrimaryStorage*,还直接用了 GetVmInstanceMetadataFromPrimaryStorage*ScanVmInstanceMetadataFromPrimaryStorage*CleanupVmInstanceMetadataOnPrimaryStorage*VmMetadataScanEntry;这些都不在当前包,当前 import 集合解析不到。

修复
 import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
 import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageReply;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageMsg;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageReply;
+import org.zstack.header.storage.primary.ScanVmInstanceMetadataFromPrimaryStorageMsg;
+import org.zstack.header.storage.primary.ScanVmInstanceMetadataFromPrimaryStorageReply;
+import org.zstack.header.storage.primary.VmMetadataScanEntry;
🤖 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 908 - 935, Add the missing import statements for the VM
metadata-related classes referenced in this file so it compiles: import the
GetVmInstanceMetadataFromPrimaryStorage* classes,
ScanVmInstanceMetadataFromPrimaryStorage* classes,
CleanupVmInstanceMetadataOnPrimaryStorage* classes, and the VmMetadataScanEntry
type (the same symbols used alongside UpdateVmInstanceMetadataOnPrimaryStorage*
and referenced in
WriteVmMetadataCmd/ScanVmMetadataRsp/GetVmInstanceMetadataRsp). Place these
imports with the other org.zstack storage/primary-related imports so the
compiler can resolve those types.

Comment on lines +337 to +372
@RequestMapping(value=LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, method= RequestMethod.POST)
public @ResponseBody
String writeVmMetadata(HttpEntity<String> entity) {
WriteVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), WriteVmMetadataCmd.class);
config.writeVmMetadataCmds.add(cmd);
reply(entity, new WriteVmMetadataRsp());
return null;
}

@RequestMapping(value=LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH, method= RequestMethod.POST)
public @ResponseBody
String getVmInstanceMetadata(HttpEntity<String> entity) {
GetVmInstanceMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVmInstanceMetadataCmd.class);
config.getVmInstanceMetadataCmds.add(cmd);
GetVmInstanceMetadataRsp rsp = new GetVmInstanceMetadataRsp();
reply(entity, rsp);
return null;
}

@RequestMapping(value=LocalStorageKvmBackend.SCAN_VM_METADATA_PATH, method= RequestMethod.POST)
public @ResponseBody
String scanVmMetadata(HttpEntity<String> entity) {
ScanVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), ScanVmMetadataCmd.class);
config.scanVmMetadataCmds.add(cmd);
ScanVmMetadataRsp rsp = new ScanVmMetadataRsp();
reply(entity, rsp);
return null;
}

@RequestMapping(value=LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH, method= RequestMethod.POST)
public @ResponseBody
String cleanupVmMetadata(HttpEntity<String> entity) {
CleanupVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CleanupVmMetadataCmd.class);
config.cleanupVmMetadataCmds.add(cmd);
reply(entity, new CleanupVmMetadataRsp());
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 | 🟠 Major

simulator 还缺少 PREFIX_REBASE_BACKING_FILES_PATH

LocalStorageKvmBackend 本次同时新增了 handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...),并固定调用 PREFIX_REBASE_BACKING_FILES_PATH。这里却只补了 4 个 metadata 接口,没有对应 @RequestMapping,所以任何覆盖 backing file prefix rebase 的 simulator 用例都会收到未处理请求。

可参考的最小补齐
+    `@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;
+    }
🤖 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 337 - 372, The simulator is missing a RequestMapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH; add a new controller
method annotated with
`@RequestMapping`(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH,
method=RequestMethod.POST) that deserializes the request body into the
appropriate command class (e.g., RebaseBackingFilesCmd), appends it to the
simulator config's collection (e.g., config.rebaseBackingFilesCmds), sends a
reply via reply(entity, new RebaseBackingFilesRsp()) and returns null so tests
invoking handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) get a handled
response. Ensure the method signature and names match existing patterns used by
writeVmMetadata/getVmInstanceMetadata/scanVmMetadata/cleanupVmMetadata.

Comment on lines +74 to +77
for (String path : allOldPaths) {
oldPrefix = VmInstanceMetadataConstants.extractOldPrefix(path);
if (oldPrefix != null) break;
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查 extractOldPrefix 的实现逻辑
ast-grep --pattern 'extractOldPrefix($$$) {
  $$$
}'

# 搜索所有使用 extractOldPrefix 的位置
rg -n 'extractOldPrefix' --type java

Repository: MatheMatrix/zstack

Length of output: 565


🏁 Script executed:

# Get the implementation of extractOldPrefix in VmInstanceMetadataConstants
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/../../../header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java

Repository: MatheMatrix/zstack

Length of output: 256


🏁 Script executed:

# Find and read VmInstanceMetadataConstants.java
fd VmInstanceMetadataConstants.java --type f -x cat -n {}

Repository: MatheMatrix/zstack

Length of output: 1103


🏁 Script executed:

# Get context around lines 74-77 in LocalStorageVmMetadataExtension.java with more surrounding code
sed -n '55,96p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java

Repository: MatheMatrix/zstack

Length of output: 2005


🏁 Script executed:

# Check the LocalStorageResourceRefVO and its constraints
rg -n 'class LocalStorageResourceRefVO' -A 20 --type java

Repository: MatheMatrix/zstack

Length of output: 4488


extractOldPrefix() 的硬编码标记匹配存在路径误识别风险。

VmInstanceMetadataConstants.extractOldPrefix() 使用硬编码的存储路径标记(/rootVolumes//dataVolumes//volumeSnapshots//memory/)通过 indexOf() 查找并提取主存储前缀。若主存储挂载路径本身包含这些子字符串(例如 /mnt/old_rootVolumes_backup/storage),则会导致提取出错误的短前缀。这将造成 calculatePathReplacements() 中的路径替换映射错误,进而影响 RebaseVolumeBackingFileOnPrimaryStorageMsg 的卷 backing file 链,可能导致数据损坏。

建议改进:从路径末尾向前扫描标记,或通过主节点查询实际的存储挂载路径来准确定位前缀界限。

🤖 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 74 - 77, The current hard-coded marker matching in
VmInstanceMetadataConstants.extractOldPrefix() can mis-identify prefixes if
those marker substrings appear earlier in a mount path; update
extractOldPrefix() to locate markers from the path end (use lastIndexOf or scan
segments from the tail) or, preferably, resolve the real primary storage mount
point via primary storage metadata and compute the prefix boundary using that
exact mount path; ensure calculatePathReplacements() continues to rely on the
corrected extractOldPrefix() behavior and that callers such as
LocalStorageVmMetadataExtension (where oldPrefix is used) and
RebaseVolumeBackingFileOnPrimaryStorageMsg receive the accurate replacements to
avoid corrupting backing-file chains.

Comment on lines +134 to +145
private void createResourceRef(String primaryStorageUuid, String resourceUuid,
String resourceType, String hostUuid, long size, Timestamp now) {
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 | 🟠 Major

createResourceRef 未检查记录是否已存在,可能导致主键冲突。

LocalStorageResourceRefVO(primaryStorageUuid, resourceUuid) 为复合主键。在 VM 元数据注册流程中,如果资源(卷或快照)已经存在于系统中并且已有对应的 LocalStorageResourceRefVO 记录,直接调用 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_.primaryStorageUuid, primaryStorageUuid)
+            .eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid)
+            .isExists();
+    if (exists) {
+        logger.debug(String.format("LocalStorageResourceRefVO already exists for resource[uuid:%s] on PS[uuid:%s], skip creation",
+                resourceUuid, primaryStorageUuid));
+        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);
 }
🤖 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 134 - 145, createResourceRef currently unconditionally calls
dbf.persist on a new LocalStorageResourceRefVO which can cause a primary-key
conflict for the composite key (primaryStorageUuid, resourceUuid); modify
createResourceRef to first query the DB for an existing
LocalStorageResourceRefVO by primaryStorageUuid and resourceUuid (e.g. via
dbf.findByExample or a specific query), and if a record exists update its fields
(size, hostUuid, lastOpDate, etc.) and call dbf.update, otherwise persist the
new ref with dbf.persist; keep the method signature and ensure you use
LocalStorageResourceRefVO and dbf to locate and save/update the record.

Comment on lines +990 to +994
public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
public List<String> filePaths;
public String oldPrefix;
public String newPrefix;
}

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

为前缀重写命令补充挂载路径上下文,避免误重写 backing chain。

Line 990-994 仅传 oldPrefix/newPrefix,一旦上游前缀提取误判,agent 侧无法二次校验,会把错误映射直接应用到 backing files。建议在命令里补充源/目标 PS 挂载路径并在执行前校验 filePaths 与前缀一致性。

建议补充字段(最小改动)
 public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
     public List<String> filePaths;
     public String oldPrefix;
     public String newPrefix;
+    public String srcPsMountPath;
+    public String dstPsMountPath;
 }
📝 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
public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
public List<String> filePaths;
public String oldPrefix;
public String newPrefix;
}
public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
public List<String> filePaths;
public String oldPrefix;
public String newPrefix;
public String srcPsMountPath;
public String dstPsMountPath;
}
🤖 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/NfsPrimaryStorageKVMBackendCommands.java`
around lines 990 - 994, The PrefixRebaseBackingFilesCmd currently only carries
filePaths, oldPrefix and newPrefix which allows an upstream prefix mis-detection
to be blindly applied; add source and target primary-storage mount path fields
(e.g., srcMountPath and destMountPath) to PrefixRebaseBackingFilesCmd and update
the agent handler that performs the rebasing to validate that each entry in
filePaths actually starts with oldPrefix and that the provided
srcMountPath/destMountPath match the agent’s mounted PS path before making any
changes; if any filePath fails the prefix check or mount-path mismatch, reject
the command with an error instead of applying the rewrite to backing files.

Comment on lines +604 to +618
simulator(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH) {
return new LocalStorageKvmBackend.WriteVmMetadataRsp()
}

simulator(LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH) {
return new LocalStorageKvmBackend.GetVmInstanceMetadataRsp()
}

simulator(LocalStorageKvmBackend.SCAN_VM_METADATA_PATH) {
return new LocalStorageKvmBackend.ScanVmMetadataRsp()
}

simulator(LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH) {
return new LocalStorageKvmBackend.CleanupVmMetadataRsp()
}

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

补上 PREFIX_REBASE_BACKING_FILES_PATH 的 testlib 模拟端点。

同一个 PR 里 LocalStorageKvmBackend 已新增 PREFIX_REBASE_BACKING_FILES_PATH,并在 handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) 中实际调用;这里却只注册了 4 个 metadata 路径。走本地存储 backing file prefix rebase 的用例在 LocalStorageSpec 环境下会直接打不到模拟 agent。

可参考的最小补齐
+            simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH) {
+                return new LocalStorageKvmBackend.PrefixRebaseBackingFilesRsp(rebasedCount: 0)
+            }
🤖 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 - 618, The test harness is missing a simulator endpoint for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH used by
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg,...), so add a simulator(...)
block in LocalStorageSpec.groovy that registers PREFIX_REBASE_BACKING_FILES_PATH
and returns the matching response object (e.g. new
LocalStorageKvmBackend.RebaseVolumeBackingFileOnPrimaryStorageRsp()) so tests
exercising backing-file prefix rebases hit the mock agent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java (1)

17-29: ⚠️ Potential issue | 🔴 Critical

resolver bean "snapshotGroupUuidToVmUuid" 未在代码库中实现,将导致运行时错误。

@MetadataImpact 注解指定的 resolver bean "snapshotGroupUuidToVmUuid" 不存在于代码库中:

  • VmUuidFromApiResolver 接口已定义,但没有任何实现类
  • 该 resolver 在 4 个快照组 API 消息类中被引用(APIRevertVmFromSnapshotGroupMsg、APIDeleteVolumeSnapshotGroupMsg、APIUpdateVolumeSnapshotGroupMsg、APIUngroupVolumeSnapshotGroupMsg)
  • 当元数据框架在运行时尝试加载该 bean 时,将抛出 NoSuchBeanDefinitionException

需要采取以下措施之一:

  1. 实现 VmUuidFromApiResolver 接口并将其注册为名称为 "snapshotGroupUuidToVmUuid" 的 Spring bean
  2. 如果暂不需要元数据跟踪,从所有快照组 API 消息中移除 @MetadataImpact 注解
🤖 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/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java`
around lines 17 - 29, The Metadata resolver "snapshotGroupUuidToVmUuid"
referenced by the `@MetadataImpact` annotation is missing and causes runtime
NoSuchBeanDefinitionException; either implement a Spring bean that implements
the VmUuidFromApiResolver interface and register it with the exact bean name
"snapshotGroupUuidToVmUuid" (create a class implementing VmUuidFromApiResolver
and annotate it as a Spring component/service with name
"snapshotGroupUuidToVmUuid") so the resolver is available for
APIRevertVmFromSnapshotGroupMsg, APIDeleteVolumeSnapshotGroupMsg,
APIUpdateVolumeSnapshotGroupMsg and APIUngroupVolumeSnapshotGroupMsg, or remove
the `@MetadataImpact` annotation from those four message classes if metadata
tracking is not required.
♻️ Duplicate comments (7)
testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy (1)

604-618: ⚠️ Potential issue | 🟠 Major

补上 PREFIX_REBASE_BACKING_FILES_PATH 的 testlib 模拟端点。

Line 604-618 这里只注册了 4 个 metadata 路径,但同一批改动里的 backing-file prefix rebase 路径在 LocalStorageSpec 里仍然没有模拟。覆盖 RebaseVolumeBackingFileOnPrimaryStorageMsg 的用例会直接命中未处理请求。

🤖 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 - 618, 在 LocalStorageSpec 的模拟注册块中缺少对
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH 的模拟端点,导致覆盖
RebaseVolumeBackingFileOnPrimaryStorageMsg 的测试直接触发未处理的请求;在与
WRITE_VM_METADATA_PATH / GET_VM_INSTANCE_METADATA_PATH / SCAN_VM_METADATA_PATH /
CLEANUP_VM_METADATA_PATH 相同的位置添加一个
simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH)
条目并返回一个适当的响应对象(与其它模拟一致地返回对应的 LocalStorageKvmBackend 的响应类实例),以确保
RebaseVolumeBackingFileOnPrimaryStorageMsg 用例被正确拦截和处理。
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)

1989-1997: ⚠️ Potential issue | 🟠 Major

不要把 getConnectedHostForOperation() 当成“返回空列表”的 API。

Line 1991、Line 2016、Line 2041 和 Line 2066 都是先调用再判 isEmpty(),但同文件 Line 461-470 已经按抛 OperationFailureException 的语义处理过了。这里一旦无可用主机,异常会在 bus.reply(...) 前直接冒出,Get/Scan/Cleanup/Rebase 四个消息都可能拿不到错误回复。

🐛 建议修复
-        List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
-        if (connectedHosts.isEmpty()) {
+        List<HostInventory> connectedHosts;
+        try {
+            connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+        } catch (OperationFailureException e) {
             reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
             bus.reply(msg, reply);
             return;
-        }
+        }

Also applies to: 2014-2023, 2039-2048, 2064-2073

🤖 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 1989 - 1997, The handlers (e.g.,
handle(GetVmInstanceMetadataFromPrimaryStorageMsg)) call
factory.getConnectedHostForOperation(...) and then check isEmpty(), but that
factory method can throw an OperationFailureException (per earlier code paths),
causing the method to throw before sending a reply; wrap the call in a try-catch
for OperationFailureException (and other relevant runtime exceptions), create
and populate the corresponding reply
(GetVmInstanceMetadataFromPrimaryStorageReply and the other reply types for
Scan/Cleanup/Rebase handlers), set reply.setError(...) using the caught
exception details, and bus.reply(msg, reply) in the catch so every handler
always returns an error reply instead of letting the exception propagate.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)

337-372: ⚠️ Potential issue | 🟠 Major

simulator 还缺 PREFIX_REBASE_BACKING_FILES_PATH

Line 337-372 这里只补了 4 个 metadata @RequestMapping,但同一批改动里的 backing-file prefix rebase agent path 仍未注册。覆盖 RebaseVolumeBackingFileOnPrimaryStorageMsg 的 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 337 - 372, The simulator is missing a request mapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH so requests from
RebaseVolumeBackingFileOnPrimaryStorageMsg are unhandled; add a `@RequestMapping`
handler in LocalStorageSimulator that listens on
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, deserializes the
incoming command into the correct Cmd class used by the rebase agent, appends it
to the simulator config list (e.g. config.prefixRebaseBackingFilesCmds or the
appropriate list), and sends back the corresponding Rsp via reply(entity, new
XxxRsp()) so the simulator test for RebaseVolumeBackingFileOnPrimaryStorageMsg
is handled.
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)

1863-1866: ⚠️ Potential issue | 🟡 Minor

先处理主存储已删除的竞态,避免误报为 unsupported

Line 1863 的 findValue() 可能返回 null。现在这里会把“主存储不存在”误报成 type null does not support metadata,并把 null 继续传给 getExtensionFromMap()。建议先对 psType 做空值短路。

建议修复
-        String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue();
-        VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class);
+        String psType = Q.New(PrimaryStorageVO.class)
+                .select(PrimaryStorageVO_.type)
+                .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid())
+                .findValue();
+        if (psType == null) {
+            evt.setError(operr("primary storage[uuid:%s] not found", msg.getPrimaryStorageUuid()));
+            bus.publish(evt);
+            return;
+        }
+
+        VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class);
         if (ext == null) {
             evt.setError(Platform.operr("primary storage type %s does not support metadata", psType));
             bus.publish(evt);
             return;
         }

As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法".

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

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`
around lines 1863 - 1866, The code uses Q.New(...).findValue() to get psType and
passes it to pluginRgty.getExtensionFromMap() without checking for null, which
misreports deleted/nonexistent primary storage as "type null does not support
metadata"; add an early-null check after String psType = Q.New(...).findValue()
to handle a missing primary storage UUID (e.g. set
evt.setError(Platform.operr(...)) and return) before calling
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
so you don't pass null into getExtensionFromMap() or produce a misleading error.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)

3924-3928: ⚠️ Potential issue | 🔴 Critical

缺少 hostUuid 空值短路会让元数据清理链路直接失败

Line 3928 在 hostUuid 可能为空时仍然发起 httpCall()。本地存储场景下,这会让从未启动过就被删除的 VM 在清理元数据时直接失败,而不是平滑跳过/交给后续兜底逻辑处理。

建议修复
 `@Override`
 void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+    if (StringUtils.isBlank(hostUuid)) {
+        logger.warn(String.format("skip cleanup vm metadata on local primary storage[uuid:%s], metadataPath:%s because hostUuid is null/blank",
+                self.getUuid(), msg.getMetadataPath()));
+        completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply());
+        return;
+    }
+
     CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
     cmd.metadataPath = msg.getMetadataPath();
 
     httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) {

As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法".

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

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3924 - 3928, In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg
msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) add an early-null check for hostUuid before creating the
CleanupVmMetadataCmd or calling httpCall(CLEANUP_VM_METADATA_PATH,...); if
hostUuid is null, short-circuit by completing successfully (e.g.
completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply())) or
otherwise returning so the cleanup is skipped/handled by downstream fallback
logic, avoiding the failing httpCall when hostUuid is absent.
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (2)

134-145: ⚠️ Potential issue | 🟠 Major

createResourceRef() 需要做幂等。

这里每次都新建 LocalStorageResourceRefVOdbf.persist()。注册、恢复或重试时,只要同一 (primaryStorageUuid, resourceUuid) 已经存在,就会直接撞主键,把元数据导入流程打断。建议先查现有记录,存在则更新 hostUuidsizelastOpDate,不存在再插入。

🤖 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 134 - 145, The createResourceRef method currently always inserts a
new LocalStorageResourceRefVO which breaks on duplicate primary keys for the
same (primaryStorageUuid, resourceUuid); make createResourceRef idempotent by
first querying for an existing LocalStorageResourceRefVO by primaryStorageUuid
and resourceUuid, and if found update its hostUuid, size and lastOpDate (and
persist/merge/update as appropriate), otherwise construct and persist a new
LocalStorageResourceRefVO; use the existing dbf query/update patterns (e.g.,
find by fields then dbf.update or dbf.persist) so retries/restore operations do
not throw unique-key violations.

72-83: ⚠️ Potential issue | 🔴 Critical

不要直接信任 extractOldPrefix() 的结果。

这里把 VmInstanceMetadataConstants.extractOldPrefix(path) 的返回值直接拿来做 substring() 切分。只要主存储挂载路径本身含有 /rootVolumes//dataVolumes/ 之类的标记,oldPrefix 就可能被截短,错误会扩散到整个 pathMap,最终把 metadata 中的路径重写到错误位置,后面的 backing file rebase 也会跟着改坏。建议改成基于真实主存储挂载点计算边界,或至少从路径尾部匹配 marker。

🤖 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 72 - 83, The code currently trusts
VmInstanceMetadataConstants.extractOldPrefix(path) and uses its result
(oldPrefix) for substring slicing when building pathMap, which can produce
incorrect prefixes if extractOldPrefix returns a truncated match; instead,
compute the split boundary from the actual path content (e.g., locate the last
occurrence of the known volume markers like "/rootVolumes/" or "/dataVolumes/"
or derive the mount point boundary from the real primary storage mount path) and
only treat a path as matching if the marker occurs at the expected boundary;
update the loop that builds pathMap (references: allOldPaths, oldPrefix,
pathMap, newPrefix, VmInstanceMetadataConstants.extractOldPrefix) to find the
marker with lastIndexOf or validate against the actual storage mount point
before calling substring so you never slice using an unverified oldPrefix.
🧹 Nitpick comments (4)
sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java (2)

31-35: 原始类型 List 缺少泛型参数

systemTagsuserTags 使用了原始类型 java.util.List,缺少泛型参数,这会导致类型安全问题。根据 ZStack SDK 的常见用法,这些字段通常应为 List<String> 类型。

如果这是 SDK 代码生成器的统一输出格式,建议在生成器侧修复此问题以提升类型安全性。

♻️ 建议添加泛型参数
 `@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/GetVmInstanceMetadataFromPrimaryStorageAction.java`
around lines 31 - 35, The fields systemTags and userTags in
GetVmInstanceMetadataFromPrimaryStorageAction use raw java.util.List; change
their declarations to use generics (java.util.List<String>) to ensure type
safety, update any corresponding imports or usages (constructors,
getters/setters, serialization code) to accept List<String>, and if this pattern
is generated by the SDK generator, update the generator template for these
fields to emit List<String> instead of raw List.

3-5: 冗余的通配符导入

org.zstack.sdk.* 导入是冗余的,因为当前类已经在 org.zstack.sdk 包中。同包内的类会自动可见。

不过,如果这是 SDK 代码生成器的标准输出模式,可以保持一致性而忽略此问题。

♻️ 建议移除冗余导入
 package org.zstack.sdk;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.zstack.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 3 - 5, 移除冗余的通配符导入:在 GetVmInstanceMetadataFromPrimaryStorageAction
类中删除 import org.zstack.sdk.*,因为类已位于 org.zstack.sdk 包内且同包类自动可见;保留需要的显式导入(例如
java.util.HashMap,
java.util.Map)或恢复任何特定类的显式导入,如果这是代码生成器的约定且需要保持一致性则可选择保留但最好改为显式导入以避免冗余。
sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java (1)

29-35: 建议将原始 List 改为 List<String>,与服务端字段保持强类型一致

Line 29、Line 32、Line 35 当前为 raw java.util.List。这会弱化 SDK 编译期约束,允许非字符串元素混入,问题会推迟到运行期才暴露。建议改为 List<String>

建议修改
-    public java.util.List vmUuids;
+    public java.util.List<String> vmUuids;

-    public java.util.List systemTags;
+    public java.util.List<String> systemTags;

-    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/CleanupVmInstanceMetadataAction.java` around
lines 29 - 35, The three public fields vmUuids, systemTags, and userTags in
CleanupVmInstanceMetadataAction are declared as raw java.util.List which weakens
compile-time type checks; change their types to java.util.List<String> in the
CleanupVmInstanceMetadataAction class (and update any related getters/setters or
usages in serialization/deserialization calls) so the SDK enforces String
elements consistent with the server-side contract.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)

7-9: 建议为接口方法添加 Javadoc 注释。

根据编码规范,接口方法应配有有效的 Javadoc 注释。建议为 getPrimaryStorageType()calculatePathReplacements() 添加文档说明,描述参数含义和返回值语义。

📝 建议的 Javadoc
 public interface VmMetadataPathReplacementExtensionPoint {
+    /**
+     * Returns the primary storage type this extension handles.
+     * `@return` primary storage type identifier
+     */
     String getPrimaryStorageType();
+
+    /**
+     * Calculates path replacements for migrating metadata paths from old prefix to new prefix.
+     * `@param` targetPsUuid the target primary storage UUID
+     * `@param` allOldPaths list of old paths to be replaced
+     * `@return` result containing old-to-new path mappings and prefix information
+     */
     PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);
🤖 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 7 - 9, The interface VmMetadataPathReplacementExtensionPoint lacks
Javadoc on its methods; add concise Javadoc to getPrimaryStorageType()
explaining it returns the primary storage type identifier used by the extension,
and to calculatePathReplacements(String targetPsUuid, List<String> allOldPaths)
describing the parameters (targetPsUuid = destination primary storage UUID,
allOldPaths = list of existing metadata paths) and the return semantics
(PathReplacementResult containing mapping of old→new paths and any
status/errors). Include `@param` and `@return` tags and a brief description of
expected behavior for edge cases (e.g., empty list handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2168-2177: The handler method
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) must guard against a null/empty hostUuid before calling
hmsg.setHostUuid(...) and bus.makeTargetServiceIdByResourceUuid(...); update the
method to check hostUuid and if it's null/empty either select a fallback mounted
KVM host for this NFS primary (query available hosts/mounts) and set that host's
UUID on the KVMHostAsyncHttpCallMsg (and target service id) or, if none
available, skip direct routing and instead enqueue the cleanup for GC/retry
(return a success or scheduled response) so routing does not fail; reference
CLEANUP_VM_METADATA_PATH, KVMHostAsyncHttpCallMsg, setHostUuid, and
bus.makeTargetServiceIdByResourceUuid when making the changes.

---

Outside diff comments:
In
`@header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java`:
- Around line 17-29: The Metadata resolver "snapshotGroupUuidToVmUuid"
referenced by the `@MetadataImpact` annotation is missing and causes runtime
NoSuchBeanDefinitionException; either implement a Spring bean that implements
the VmUuidFromApiResolver interface and register it with the exact bean name
"snapshotGroupUuidToVmUuid" (create a class implementing VmUuidFromApiResolver
and annotate it as a Spring component/service with name
"snapshotGroupUuidToVmUuid") so the resolver is available for
APIRevertVmFromSnapshotGroupMsg, APIDeleteVolumeSnapshotGroupMsg,
APIUpdateVolumeSnapshotGroupMsg and APIUngroupVolumeSnapshotGroupMsg, or remove
the `@MetadataImpact` annotation from those four message classes if metadata
tracking is not required.

---

Duplicate comments:
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3924-3928: In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg
msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) add an early-null check for hostUuid before creating the
CleanupVmMetadataCmd or calling httpCall(CLEANUP_VM_METADATA_PATH,...); if
hostUuid is null, short-circuit by completing successfully (e.g.
completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply())) or
otherwise returning so the cleanup is skipped/handled by downstream fallback
logic, avoiding the failing httpCall when hostUuid is absent.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 337-372: The simulator is missing a request mapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH so requests from
RebaseVolumeBackingFileOnPrimaryStorageMsg are unhandled; add a `@RequestMapping`
handler in LocalStorageSimulator that listens on
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, deserializes the
incoming command into the correct Cmd class used by the rebase agent, appends it
to the simulator config list (e.g. config.prefixRebaseBackingFilesCmds or the
appropriate list), and sends back the corresponding Rsp via reply(entity, new
XxxRsp()) so the simulator test for RebaseVolumeBackingFileOnPrimaryStorageMsg
is handled.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 134-145: The createResourceRef method currently always inserts a
new LocalStorageResourceRefVO which breaks on duplicate primary keys for the
same (primaryStorageUuid, resourceUuid); make createResourceRef idempotent by
first querying for an existing LocalStorageResourceRefVO by primaryStorageUuid
and resourceUuid, and if found update its hostUuid, size and lastOpDate (and
persist/merge/update as appropriate), otherwise construct and persist a new
LocalStorageResourceRefVO; use the existing dbf query/update patterns (e.g.,
find by fields then dbf.update or dbf.persist) so retries/restore operations do
not throw unique-key violations.
- Around line 72-83: The code currently trusts
VmInstanceMetadataConstants.extractOldPrefix(path) and uses its result
(oldPrefix) for substring slicing when building pathMap, which can produce
incorrect prefixes if extractOldPrefix returns a truncated match; instead,
compute the split boundary from the actual path content (e.g., locate the last
occurrence of the known volume markers like "/rootVolumes/" or "/dataVolumes/"
or derive the mount point boundary from the real primary storage mount path) and
only treat a path as matching if the marker occurs at the expected boundary;
update the loop that builds pathMap (references: allOldPaths, oldPrefix,
pathMap, newPrefix, VmInstanceMetadataConstants.extractOldPrefix) to find the
marker with lastIndexOf or validate against the actual storage mount point
before calling substring so you never slice using an unverified oldPrefix.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1989-1997: The handlers (e.g.,
handle(GetVmInstanceMetadataFromPrimaryStorageMsg)) call
factory.getConnectedHostForOperation(...) and then check isEmpty(), but that
factory method can throw an OperationFailureException (per earlier code paths),
causing the method to throw before sending a reply; wrap the call in a try-catch
for OperationFailureException (and other relevant runtime exceptions), create
and populate the corresponding reply
(GetVmInstanceMetadataFromPrimaryStorageReply and the other reply types for
Scan/Cleanup/Rebase handlers), set reply.setError(...) using the caught
exception details, and bus.reply(msg, reply) in the catch so every handler
always returns an error reply instead of letting the exception propagate.

In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1866: The code uses Q.New(...).findValue() to get psType and
passes it to pluginRgty.getExtensionFromMap() without checking for null, which
misreports deleted/nonexistent primary storage as "type null does not support
metadata"; add an early-null check after String psType = Q.New(...).findValue()
to handle a missing primary storage UUID (e.g. set
evt.setError(Platform.operr(...)) and return) before calling
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
so you don't pass null into getExtensionFromMap() or produce a misleading error.

In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-618: 在 LocalStorageSpec 的模拟注册块中缺少对
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH 的模拟端点,导致覆盖
RebaseVolumeBackingFileOnPrimaryStorageMsg 的测试直接触发未处理的请求;在与
WRITE_VM_METADATA_PATH / GET_VM_INSTANCE_METADATA_PATH / SCAN_VM_METADATA_PATH /
CLEANUP_VM_METADATA_PATH 相同的位置添加一个
simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH)
条目并返回一个适当的响应对象(与其它模拟一致地返回对应的 LocalStorageKvmBackend 的响应类实例),以确保
RebaseVolumeBackingFileOnPrimaryStorageMsg 用例被正确拦截和处理。

---

Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 7-9: The interface VmMetadataPathReplacementExtensionPoint lacks
Javadoc on its methods; add concise Javadoc to getPrimaryStorageType()
explaining it returns the primary storage type identifier used by the extension,
and to calculatePathReplacements(String targetPsUuid, List<String> allOldPaths)
describing the parameters (targetPsUuid = destination primary storage UUID,
allOldPaths = list of existing metadata paths) and the return semantics
(PathReplacementResult containing mapping of old→new paths and any
status/errors). Include `@param` and `@return` tags and a brief description of
expected behavior for edge cases (e.g., empty list handling).

In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java`:
- Around line 29-35: The three public fields vmUuids, systemTags, and userTags
in CleanupVmInstanceMetadataAction are declared as raw java.util.List which
weakens compile-time type checks; change their types to java.util.List<String>
in the CleanupVmInstanceMetadataAction class (and update any related
getters/setters or usages in serialization/deserialization calls) so the SDK
enforces String elements consistent with the server-side contract.

In
`@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java`:
- Around line 31-35: The fields systemTags and userTags in
GetVmInstanceMetadataFromPrimaryStorageAction use raw java.util.List; change
their declarations to use generics (java.util.List<String>) to ensure type
safety, update any corresponding imports or usages (constructors,
getters/setters, serialization code) to accept List<String>, and if this pattern
is generated by the SDK generator, update the generator template for these
fields to emit List<String> instead of raw List.
- Around line 3-5: 移除冗余的通配符导入:在 GetVmInstanceMetadataFromPrimaryStorageAction
类中删除 import org.zstack.sdk.*,因为类已位于 org.zstack.sdk 包内且同包类自动可见;保留需要的显式导入(例如
java.util.HashMap,
java.util.Map)或恢复任何特定类的显式导入,如果这是代码生成器的约定且需要保持一致性则可选择保留但最好改为显式导入以避免冗余。
🪄 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: 7e8a0351-ecc2-4481-be8d-de571aa9ea98

📥 Commits

Reviewing files that changed from the base of the PR and between 295ef96 and 520245e.

⛔ 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 (148)
  • 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/upgrade/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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • 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/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/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/APIDeleteVmNicMsg.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/APIExpungeVmInstanceMsg.java
  • 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/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/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/APICreateVmCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • 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/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.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
✅ Files skipped from review due to trivial changes (91)
  • sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
  • testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
  • sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.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/vm/APISetVmSoundTypeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
  • header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
  • sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
  • header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
  • resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • conf/db/upgrade/V5.0.0__schema.sql
  • sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
  • sdk/src/main/java/SourceClassMap.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
  • compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
  • header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java
🚧 Files skipped from review as they are similar to previous changes (35)
  • header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
  • header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
  • header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
  • header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
  • header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
  • header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
  • header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
  • sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java
  • plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
  • plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
  • testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy

Comment on lines +2168 to +2177
public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.setUuid(msg.getPrimaryStorageUuid());
cmd.metadataPath = msg.getMetadataPath();

KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
hmsg.setCommand(cmd);
hmsg.setPath(CLEANUP_VM_METADATA_PATH);
hmsg.setHostUuid(hostUuid);
bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);

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

hostUuid 为空时不要继续路由清理请求。

未启动过就被删除的 VM 可能拿不到 hostUuid。这里直接把它用于 setHostUuid()makeTargetServiceIdByResourceUuid(),会在路由阶段就把清理流程打断。对 NFS 来说更稳的是回退到任意可用挂载主机;至少也要先判空,再选择跳过本次清理或交给 GC。

🛠️ 一个最小兜底写法
 `@Override`
 public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+    if (StringUtils.isBlank(hostUuid)) {
+        logger.warn(String.format("skip cleaning vm metadata[path:%s] on nfs primary storage[uuid:%s] because no hostUuid is available",
+                msg.getMetadataPath(), msg.getPrimaryStorageUuid()));
+        completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply());
+        return;
+    }
+
     CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
     cmd.setUuid(msg.getPrimaryStorageUuid());
     cmd.metadataPath = msg.getMetadataPath();
🤖 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 2168 - 2177, The handler method
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) must guard against a null/empty hostUuid before calling
hmsg.setHostUuid(...) and bus.makeTargetServiceIdByResourceUuid(...); update the
method to check hostUuid and if it's null/empty either select a fallback mounted
KVM host for this NFS primary (query available hosts/mounts) and set that host's
UUID on the KVMHostAsyncHttpCallMsg (and target service id) or, if none
available, skip direct routing and instead enqueue the cleanup for GC/retry
(return a success or scheduled response) so routing does not fail; reference
CLEANUP_VM_METADATA_PATH, KVMHostAsyncHttpCallMsg, setHostUuid, and
bus.makeTargetServiceIdByResourceUuid when making the changes.

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.

4 participants