Skip to content

<fix>[vm]: add MetadataImpact#3634

Open
zstack-robot-2 wants to merge 1 commit intofeature-zsv-5.0.0-vm-registrationfrom
sync/tao.gan/ZSV-11559@@3
Open

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

Conversation

@zstack-robot-2
Copy link
Copy Markdown
Collaborator

APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373

sync from gitlab !9494

APIImpact

Resolves: ZSV-11559

Change-Id: I6b6a6378627264646d6a76726762736e77787373
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

简述

此次变更为虚拟机系统引入了元数据管理框架,包括数据库表定义、API接口、垃圾回收机制、业务流程、存储适配实现及SDK支持,涵盖元数据的注册、清理、扫描、更新等全生命周期操作。

变更总览

变更分组 / 文件(s) 摘要
元数据核心定义
header/src/main/java/org/zstack/header/vm/metadata/*
新增元数据DTO类、服务接口、错误枚举、扩展点接口定义,以及VO实体及其JPA元模型,建立元数据管理的基础数据结构
元数据API消息和事件
header/src/main/java/org/zstack/header/vm/metadata/API*Msg.java, header/src/main/java/org/zstack/header/vm/metadata/API*Event.java, header/src/main/java/org/zstack/header/vm/metadata/API*MsgDoc_zh_cn.groovy
定义"清理、注册、获取、更新、扫描"元数据的REST API接口及对应事件,包括中文文档
存储操作消息
header/src/main/java/org/zstack/header/storage/primary/*Msg.java, header/src/main/java/org/zstack/header/storage/primary/*Reply.java
添加主存储元数据读取、清理、获取、扫描的消息和回复类
@MetadataImpact 注解应用
header/src/main/java/org/zstack/header/vm/*.java, header/src/main/java/org/zstack/header/volume/*.java, header/src/main/java/org/zstack/header/tag/*.java, header/src/main/java/org/zstack/header/storage/snapshot/*, plugin/localstorage/...APILocalStorageMigrateVolumeMsg.java, resourceconfig/...APIDeleteResourceConfigMsg.java
为近50个API消息类添加元数据影响声明注解,标记操作对VM元数据的影响类型(CONFIG/STORAGE)及解析策略
虚拟机状态和系统标签
header/src/main/java/org/zstack/header/vm/VmInstanceState.java, header/src/main/java/org/zstack/header/vm/VmSystemTags.java
新增Registering虚拟机状态、VM_METADATA_REGISTERING_MN_UUID系统标签
元数据业务实现
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/VmInstanceApiInterceptor.java, compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
实现元数据清理GC、清理流程、API拦截验证、12个全局配置项
数据库模式
conf/db/upgrade/V5.0.0__schema.sql
创建VmMetadataDirtyVOVmMetadataFingerprintVO两张表,分别跟踪元数据脏状态和指纹
LocalStorage 实现
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java, plugin/localstorage/.../LocalStorageHypervisorBackend.java, plugin/localstorage/.../LocalStorageKvmBackend.java, plugin/localstorage/.../LocalStorageSimulator*.java, plugin/localstorage/.../LocalStorageVmMetadataExtension.java
本地存储层实现元数据路径构建、CRUD操作、前缀重基准、模拟器
NFS存储实现
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java, plugin/nfsPrimaryStorage/.../NfsPrimaryStorageBackend.java, plugin/nfsPrimaryStorage/.../NfsPrimaryStorageKVMBackend.java, plugin/nfsPrimaryStorage/.../NfsPrimaryStorageKVMBackendCommands.java, plugin/nfsPrimaryStorage/.../NfsVmMetadataExtension.java
NFS存储层实现元数据路径构建、命令处理、路径替换
主存储基础
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
添加API扫描元数据消息处理,包括路径构建、扫描、过滤
SDK 客户端
sdk/src/main/java/org/zstack/sdk/*Action.java, sdk/src/main/java/org/zstack/sdk/*Result.java, sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java, sdk/src/main/java/SourceClassMap.java
新增5个操作类(清理、获取、注册、更新、扫描)及对应结果类和数据传输对象
测试框架
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
添加API辅助方法、模拟器端点注册

预估代码审查工作量

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

诗意赞颂

🐰 元数据的舞蹈,跨越存储的边界,
脏页追踪指纹,GC回收优雅而又智慧,
从LocalStorage到NFS,从消息到流程,
虚拟机的灵魂得以记录与还原,
✨ 一场完整的数据之旅 ✨

✨ 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@@3

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

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

9-24: 可考虑使用 Stream 简化批量处理逻辑

根据编码规范建议使用 Java Stream 或 Lambda 表达式代替冗长的循环与条件判断。当前实现正确,但可以更简洁。

♻️ 使用 Stream 的简化版本
     default List<String> batchResolveVmUuids(List<String> fieldValues) {
-        List<String> result = new ArrayList<>();
         if (fieldValues == null || fieldValues.isEmpty()) {
-            return result;
+            return new ArrayList<>();
         }
-        for (String v : fieldValues) {
-            if (v == null) {
-                continue;
-            }
-            String vmUuid = resolveVmUuid(v);
-            if (vmUuid != null) {
-                result.add(vmUuid);
-            }
-        }
-        return result;
+        return fieldValues.stream()
+                .filter(v -> v != null)
+                .map(this::resolveVmUuid)
+                .filter(uuid -> uuid != null)
+                .collect(java.util.stream.Collectors.toList());
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`
around lines 9 - 24, The batchResolveVmUuids method uses an explicit loop and
null checks that can be simplified with Java Streams: replace the for-loop in
batchResolveVmUuids with a stream pipeline on fieldValues that filters out
nulls, maps each value via resolveVmUuid, filters out null results, and collects
to a List<String>; ensure you still return an empty list when fieldValues is
null or empty and keep the method signature and resolveVmUuid call unchanged.
header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java (1)

5-5: 字段命名 vo 含义不够明确

根据编码规范,应避免使用含糊的缩写。vo 可能表示 "Value Object" 或其他含义,建议使用更具描述性的名称,如 valueObject 或根据实际业务含义命名(例如 voJsonvoSerialized 等)。

♻️ 建议的命名改进
-    private String vo;
+    private String voJson;  // 或其他更具描述性的名称
🤖 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` at
line 5, The field name vo in class ResourceMetadata is ambiguous; rename it to a
descriptive identifier (e.g., valueObject, voJson, or voSerialized depending on
its actual content) and update all related symbols: the private field, its
getter/setter methods (e.g., getVo()/setVo() ->
getValueObject()/setValueObject()), any constructors, builders, equals/hashCode,
toString, serialization annotations, and usages across the codebase to match the
new name to avoid breaking references; also update persistence/JSON mapping
annotations or column names if present so external contracts remain consistent.
header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java (1)

24-24: 考虑添加 updateOnFailure = true 以保持一致性。

APIDeleteVolumeSnapshotMsgAPIRevertVolumeFromSnapshotMsg 都设置了 updateOnFailure = true,但此处的 APIShrinkVolumeSnapshotMsg 未设置。如果 shrink 操作失败时也需要更新元数据状态,建议添加该属性以保持 snapshot 相关 API 的行为一致。

建议的修改
-@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotUuidToVmUuidResolver", field = "uuid")
+@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotUuidToVmUuidResolver", field = "uuid", updateOnFailure = true)
🤖 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/APIShrinkVolumeSnapshotMsg.java`
at line 24, Add updateOnFailure = true to the `@MetadataImpact` annotation on
APIShrinkVolumeSnapshotMsg so its metadata update behavior matches
APIDeleteVolumeSnapshotMsg and APIRevertVolumeFromSnapshotMsg; locate the
`@MetadataImpact` on class APIShrinkVolumeSnapshotMsg and include the
updateOnFailure = true attribute (i.e., `@MetadataImpact`(value =
MetadataImpact.Impact.STORAGE, resolver = "SnapshotUuidToVmUuidResolver", field
= "uuid", updateOnFailure = true)).
testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy (1)

525-539: 建议为元数据模拟接口增加最小状态行为。

目前四个接口都返回固定成功响应,难以覆盖元数据“写-读-扫-清理”的链路一致性校验。建议后续补一个轻量内存态或 VFS hook,提升回归检测有效性。

🤖 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/NfsPrimaryStorageSpec.groovy` around
lines 525 - 539, The four simulator handlers for NfsPrimaryStorageKVMBackend
(WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH,
CLEANUP_VM_METADATA_PATH) currently always return static success responses which
prevents testing the write-read-scan-cleanup metadata lifecycle; implement a
minimal in-memory metadata store (or VFS hook) inside the test harness and
update the simulator blocks to mutate/read that store: have the WRITE handler
record metadata keyed by vm/instance id, GET return stored metadata or
not-found, SCAN enumerate stored entries, and CLEANUP remove entries so the full
chain can be validated during tests.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java (1)

18-22: 建议将 code 设为不可变字段。

该值仅在构造时赋值,改为 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`
around lines 18 - 22, 将 VmMetadataErrors 中的可变字段 code 改为不可变,修改字段声明为 private final
String code,并在构造器 VmMetadataErrors(int id) 中按原有逻辑赋值(code =
String.format("VM_METADATA.%s", id));确保没有其它地方尝试重新赋值该字段(若有,改为使用新的常量或方法返回值替代)。
sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java (1)

6-11: 建议审查 SDK 代码生成器,为 Result 类字段添加泛型支持。

该 Result 类中 vmInstanceMetadata 字段使用了原始类型 java.util.List,而对应的源类(ScanVmInstanceMetadataFromPrimaryStorageReply)已明确使用了 List<VmMetadataScanEntry>。这是 SDK 代码生成器的一致问题——整个生成的 Result 类均采用原始类型,导致编译期类型检查和 IDE 自动完成功能的缺失。

建议修复生成器以保留泛型信息,具体改为 java.util.List<VmMetadataScanEntry>

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

In
`@sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java`
around lines 6 - 11, The generated Result class
ScanVmInstanceMetadataFromPrimaryStorageResult uses a raw java.util.List for the
field vmInstanceMetadata and its accessor methods; change the field and the
signatures of getVmInstanceMetadata and setVmInstanceMetadata to use the generic
type java.util.List<VmMetadataScanEntry> (and add/import VmMetadataScanEntry as
needed) so callers get compile-time type safety and IDE completion; update the
SDK code generator that emits this class to preserve generic type parameters
from the source reply (ScanVmInstanceMetadataFromPrimaryStorageReply ->
List<VmMetadataScanEntry>) when emitting fields and getter/setter method
signatures.
compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java (1)

37-37: 移除未使用的导入

VmInstanceMetadataConstants 在此文件中未被使用,应该删除该导入语句。

♻️ 建议的修改
-import org.zstack.header.vm.metadata.VmInstanceMetadataConstants;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java` at
line 37, Remove the unused import of VmInstanceMetadataConstants from
VmInstanceApiInterceptor: locate the import statement "import
org.zstack.header.vm.metadata.VmInstanceMetadataConstants;" and delete it so
there are no unused imports remaining in the VmInstanceApiInterceptor class.
header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java (2)

17-19: 建议在 @APIParam 中添加 checkExistence = true 验证

APIUpdateVmInstanceMetadataMsg 类似,建议添加 checkExistence = true 以在 API 层面验证 VM UUID 的存在性。

♻️ 建议的修改
-    `@APIParam`(resourceType = VmInstanceVO.class, nonempty = true)
+    `@APIParam`(resourceType = VmInstanceVO.class, nonempty = true, checkExistence = true)
     private List<String> vmUuids;
🤖 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/APICleanupVmInstanceMetadataMsg.java`
around lines 17 - 19, The API parameter vmUuids in
APICleanupVmInstanceMetadataMsg lacks existence validation; update the `@APIParam`
on the vmUuids field in the APICleanupVmInstanceMetadataMsg class to include
checkExistence = true so the API layer verifies VM UUIDs exist (match the
behavior used in APIUpdateVmInstanceMetadataMsg) by adjusting the `@APIParam`
annotation on the private List<String> vmUuids field.

29-33: 建议简化 __example__() 中的导入引用

可以在文件顶部添加 import java.util.Arrays; 然后直接使用 Arrays.asList(),或者使用已导入的 java.util.List 配合 List.of()(Java 9+)。

♻️ 建议的修改

在文件顶部添加导入:

import java.util.Arrays;

然后修改 __example__() 方法:

     public static APICleanupVmInstanceMetadataMsg __example__() {
         APICleanupVmInstanceMetadataMsg msg = new APICleanupVmInstanceMetadataMsg();
-        msg.vmUuids = java.util.Arrays.asList(uuid(), uuid());
+        msg.vmUuids = Arrays.asList(uuid(), uuid());
         return msg;
     }
🤖 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/APICleanupVmInstanceMetadataMsg.java`
around lines 29 - 33, The __example__ method in APICleanupVmInstanceMetadataMsg
uses fully qualified java.util.Arrays.asList; simplify by adding an import
(import java.util.Arrays;) at the top and replace java.util.Arrays.asList(...)
with Arrays.asList(...), or alternatively import java.util.List and use
List.of(...) to construct vmUuids more concisely; update the
APICleanupVmInstanceMetadataMsg.__example__ method to assign vmUuids using the
chosen simplified factory method.
header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java (1)

18-20: 建议在 @APIParam 中添加 checkExistence = true 验证

当前 vmUuids 参数仅设置了 nonempty = true,但未验证 UUID 是否实际存在于数据库中。建议添加 checkExistence = true 以确保在 API 层面提前校验 VM 实例的存在性。

♻️ 建议的修改
-    `@APIParam`(resourceType = VmInstanceVO.class, nonempty = true)
+    `@APIParam`(resourceType = VmInstanceVO.class, nonempty = true, checkExistence = true)
     private List<String> vmUuids;
🤖 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/APIUpdateVmInstanceMetadataMsg.java`
around lines 18 - 20, APIUpdateVmInstanceMetadataMsg 中的 vmUuids 字段仅使用了
`@APIParam`(nonempty = true) 而未验证数据库中是否存在对应 VM 实例,建议在该注解上添加 checkExistence =
true;请定位类 APIUpdateVmInstanceMetadataMsg 中的私有字段 vmUuids 并将其 `@APIParam` 注解更新为包含
checkExistence = true(保留 resourceType = VmInstanceVO.class 和 nonempty = true),以在
API 层提前校验 VM UUID 的存在性。
header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java (1)

29-32: 建议在 __example__() 中添加示例数据

当前的 __example__() 方法返回空的事件对象,建议添加示例数据以便生成更有意义的 API 文档。

📝 建议的修改
     public static APIScanVmInstanceMetadataFromPrimaryStorageEvent __example__() {
         APIScanVmInstanceMetadataFromPrimaryStorageEvent evt = new APIScanVmInstanceMetadataFromPrimaryStorageEvent();
+        VmMetadataScanEntry entry = new VmMetadataScanEntry();
+        entry.setVmUuid(uuid());
+        entry.setVmName("example-vm");
+        evt.setVmInstanceMetadata(java.util.Collections.singletonList(entry));
         return evt;
     }
🤖 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/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java`
around lines 29 - 32, The __example__() method in
APIScanVmInstanceMetadataFromPrimaryStorageEvent returns an empty event;
populate it with representative sample data to improve generated API docs by
setting relevant fields on the APIScanVmInstanceMetadataFromPrimaryStorageEvent
instance (e.g., set a sample primaryStorageUuid, a list or count of scanned VM
metadata, status or success flag if available, and any child objects like
VmInstanceMetadata entries) using the class's setters or public fields so the
example shows realistic content; locate the
APIScanVmInstanceMetadataFromPrimaryStorageEvent.__example__ method and add
example values for its identifiable properties (primaryStorageUuid, metadata
list/count, status) before returning the evt.
sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java (1)

83-85: 布尔类型的 getter 方法命名建议使用 is 前缀

按照 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 83 -
85, The boolean getter is named getIncomplete which violates JavaBean
conventions; rename the method getIncomplete() to isIncomplete() in class
VmMetadataScanEntry and update all usages/tests/serializers that reference
getIncomplete to call isIncomplete instead; ensure the existing private boolean
field incomplete remains unchanged and, if necessary for serialization
compatibility, add a `@JsonProperty`("incomplete") annotation to the new
isIncomplete() or keep a delegating getIncomplete() that calls isIncomplete() to
preserve binary/JSON compatibility.
header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java (1)

12-12: 建议初始化 failedVmUuids 字段以避免潜在的空指针问题

failedVmUuids 字段未初始化,调用 getFailedVmUuids() 可能返回 null。建议与 APIScanVmInstanceMetadataFromPrimaryStorageEvent 保持一致,初始化为空列表。

🛡️ 建议的修改
     private Integer totalCleaned;
     private Integer totalFailed;
-    private List<String> failedVmUuids;
+    private List<String> failedVmUuids = new java.util.ArrayList<>();

或者在 setter 中添加空值保护:

     public void setFailedVmUuids(List<String> failedVmUuids) {
-        this.failedVmUuids = failedVmUuids;
+        this.failedVmUuids = failedVmUuids == null ? new java.util.ArrayList<>() : failedVmUuids;
     }
🤖 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/APICleanupVmInstanceMetadataEvent.java`
at line 12, APICleanupVmInstanceMetadataEvent's field failedVmUuids is not
initialized and can be null; initialize it to an empty list (e.g. new
ArrayList<>()) where the field is declared or add null-protection in its
setter/getFailedVmUuids() to return an empty list to match
APIScanVmInstanceMetadataFromPrimaryStorageEvent; update the field declaration
or the setter/getter in class APICleanupVmInstanceMetadataEvent accordingly so
callers never receive null.
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)

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

根据编码规范,接口方法应配有有效的 Javadoc 注释,以便其他开发者理解扩展点的契约。

📝 建议添加 Javadoc
 public interface VmMetadataPathReplacementExtensionPoint {
+    /**
+     * Returns the primary storage type this extension handles.
+     * `@return` primary storage type identifier
+     */
     String getPrimaryStorageType();
+
+    /**
+     * Calculates path replacements for VM metadata migration.
+     * `@param` targetPsUuid target primary storage UUID
+     * `@param` allOldPaths list of old metadata paths to be replaced
+     * `@return` result containing 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, 为接口 VmMetadataPathReplacementExtensionPoint 的两个方法添加
Javadoc:在 getPrimaryStorageType() 的注释中说明返回值含义(应返回哪个主存储类型标识、不能为 null 的约束等);在
calculatePathReplacements(String targetPsUuid, List<String> allOldPaths)
的注释中说明每个参数的含义(targetPsUuid 表示目标主存储 UUID,allOldPaths 为需替换的原始路径列表)、预期的返回值类型
PathReplacementResult
的语义(例如如何表示替换映射或无替换情况)、异常场景或线程安全/并发调用约束以及允许的空值处理策略;确保描述清晰以形成扩展点契约。
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)

375-383: prefixRebaseBackingFiles 模拟器端点缺少命令记录。

其他模拟器端点(如 writeVmMetadatascanVmMetadata 等)都会将命令添加到 config 中以供测试验证,但 prefixRebaseBackingFiles 没有这样做。这可能导致测试无法验证该命令是否被正确调用。

♻️ 建议添加命令记录
 `@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);
+    config.prefixRebaseBackingFilesCmds.add(cmd);
     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 375 - 383, The simulator endpoint
LocalStorageSimulator.prefixRebaseBackingFiles currently constructs a
PrefixRebaseBackingFilesCmd but does not record it for test verification; update
prefixRebaseBackingFiles to add the received PrefixRebaseBackingFilesCmd (the
cmd variable) into the simulator's command log/config used by other endpoints
(same mechanism used by writeVmMetadata/scanVmMetadata) before replying with
PrefixRebaseBackingFilesRsp so tests can assert the command was invoked.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (2)

37-37: 路径末尾斜杠处理不一致。

resolveBaseDir 方法(第 37 行)移除末尾斜杠,而 calculatePathReplacements 中的 newPrefix(第 101 行)总是添加末尾斜杠:

// resolveBaseDir - 移除末尾斜杠
return baseDir.endsWith("/") ? baseDir.substring(0, baseDir.length() - 1) : baseDir;

// calculatePathReplacements - 添加末尾斜杠
String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/";

这种不一致可能是有意为之(因为 buildMetadataDir 使用 resolveBaseDir 后会在 String.format 中添加斜杠),但建议添加注释说明设计意图,以避免后续维护时产生混淆。

Also applies to: 101-101

🤖 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`
at line 37, The code treats trailing slashes inconsistently: resolveBaseDir
removes a trailing '/' while calculatePathReplacements derives newPrefix that
ensures a trailing '/', which is confusing; update the implementation to be
explicit by either (a) making both resolveBaseDir and newPrefix consistently
include or exclude the trailing slash, or (b) keep the current behavior but add
a clear comment in resolveBaseDir, calculatePathReplacements (and
buildMetadataDir) explaining the intended convention (e.g., resolveBaseDir
returns a no-trailing-slash base and other callers append '/' as needed),
referencing the resolveBaseDir, calculatePathReplacements, newPrefix and
buildMetadataDir symbols so future maintainers understand the design intent.

29-38: 建议提取公共逻辑以减少代码重复。

resolveBaseDir 方法(第 29-38 行)与 calculatePathReplacements 中的逻辑(第 91-100 行)存在重复:两处都从 PrimaryStorageVO 获取 mountPathurl

虽然错误处理策略不同(一个抛异常,一个记录警告并返回空),但可以考虑提取公共部分:

♻️ 可选重构建议
+    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(String.format("cannot find mountPath or url for NFS primary storage[uuid:%s]", primaryStorageUuid));
         }
         return baseDir.endsWith("/") ? baseDir.substring(0, baseDir.length() - 1) : baseDir;
     }

Also applies to: 91-100

🤖 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 29 - 38, Extract the duplicated logic that queries PrimaryStorageVO
for mountPath or url into a single helper (e.g., a private method like
fetchPrimaryStorageBase(String primaryStorageUuid) or getMountOrUrl) that uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() and falls back to selecting
PrimaryStorageVO_.url; have resolveBaseDir call this helper and throw the
CloudRuntimeException if it returns null, and have calculatePathReplacements
call the same helper and handle a null return by logging a warning and returning
null/empty as it currently does—this removes the duplicated query while
preserving each caller's error 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 `@conf/db/upgrade/V5.0.0__schema.sql`:
- Around line 12-13: The FOREIGN KEY REFERENCES clauses for the constraints
fkVmMetadataDirtyVOVmInstanceEO and fkVmMetadataDirtyVOManagementNodeVO (and the
other FOREIGN KEY at the same section) must explicitly qualify the referenced
tables with the zstack schema; update the REFERENCES targets from VmInstanceEO
(`uuid`) and ManagementNodeVO (`uuid`) (and the third referenced table at the
same block) to zstack.VmInstanceEO (`uuid`) and zstack.ManagementNodeVO (`uuid`)
(and zstack.<ThirdTable>) so the foreign key clauses consistently include the
zstack schema qualifier.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy`:
- Around line 108-112: Add a trailing newline at the end of the file so the file
ends with a newline character (POSIX compliance); locate the file block that
ends with the APIRegisterVmInstanceFromMetadataEvent.class response and the
closing braces and ensure there is a single blank line (newline) after the final
"}" so the file terminates with a newline.

In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Around line 24-25: The dirtyVersion field in VmMetadataDirtyVO is currently an
uninitialized primitive long (defaults to 0) which causes JPA to persist 0
instead of the DB default 1; initialize dirtyVersion to 1L at the entity level
(either via a field initializer or in the VmMetadataDirtyVO constructor) so the
value matches the database default and JPA will persist 1 instead of 0; update
the declaration of dirtyVersion and/or the class constructor accordingly.

In `@header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java`:
- Line 7: The project references a Spring bean named VolumeUuidToVmUuidResolver
via the `@MetadataImpact` annotation but that bean is missing; implement a class
VolumeUuidToVmUuidResolver that implements the VmUuidFromApiResolver interface
(or change the `@MetadataImpact` usage to point to an existing resolver) so Spring
can autowire it at startup; ensure the new class lives on the component-scan
path, is annotated appropriately (e.g., `@Component` or registered as a bean),
implements the required methods from VmUuidFromApiResolver, and is referenced by
APIDeleteDataVolumeMsg's `@MetadataImpact` configuration.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3471-3490: The calls to getHypervisorBackendFactoryByHostUuid(...)
and f.getHypervisorBackend(self) can throw if the host is detached concurrently,
which bypasses the per-host error handling; wrap the lines that obtain
LocalStorageHypervisorFactory and LocalStorageHypervisorBackend (the calls to
getHypervisorBackendFactoryByHostUuid(...) and f.getHypervisorBackend(self)) in
a try/catch inside the While callback, and on exception invoke com.addError(...)
with a converted ErrorCode (or build one), call com.done(), and log the failure,
then return so bkd.handle(...) is not called for that host; keep existing
success/fail behavior in bkd.handle(...) unchanged.

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Line 37: Remove the unused import of VmInstanceMetadataConstants from
VmInstanceApiInterceptor: locate the import statement "import
org.zstack.header.vm.metadata.VmInstanceMetadataConstants;" and delete it so
there are no unused imports remaining in the VmInstanceApiInterceptor class.

In
`@header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java`:
- Around line 29-32: The __example__() method in
APIScanVmInstanceMetadataFromPrimaryStorageEvent returns an empty event;
populate it with representative sample data to improve generated API docs by
setting relevant fields on the APIScanVmInstanceMetadataFromPrimaryStorageEvent
instance (e.g., set a sample primaryStorageUuid, a list or count of scanned VM
metadata, status or success flag if available, and any child objects like
VmInstanceMetadata entries) using the class's setters or public fields so the
example shows realistic content; locate the
APIScanVmInstanceMetadataFromPrimaryStorageEvent.__example__ method and add
example values for its identifiable properties (primaryStorageUuid, metadata
list/count, status) before returning the evt.

In
`@header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java`:
- Line 24: Add updateOnFailure = true to the `@MetadataImpact` annotation on
APIShrinkVolumeSnapshotMsg so its metadata update behavior matches
APIDeleteVolumeSnapshotMsg and APIRevertVolumeFromSnapshotMsg; locate the
`@MetadataImpact` on class APIShrinkVolumeSnapshotMsg and include the
updateOnFailure = true attribute (i.e., `@MetadataImpact`(value =
MetadataImpact.Impact.STORAGE, resolver = "SnapshotUuidToVmUuidResolver", field
= "uuid", updateOnFailure = true)).

In
`@header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java`:
- Line 12: APICleanupVmInstanceMetadataEvent's field failedVmUuids is not
initialized and can be null; initialize it to an empty list (e.g. new
ArrayList<>()) where the field is declared or add null-protection in its
setter/getFailedVmUuids() to return an empty list to match
APIScanVmInstanceMetadataFromPrimaryStorageEvent; update the field declaration
or the setter/getter in class APICleanupVmInstanceMetadataEvent accordingly so
callers never receive null.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java`:
- Around line 17-19: The API parameter vmUuids in
APICleanupVmInstanceMetadataMsg lacks existence validation; update the `@APIParam`
on the vmUuids field in the APICleanupVmInstanceMetadataMsg class to include
checkExistence = true so the API layer verifies VM UUIDs exist (match the
behavior used in APIUpdateVmInstanceMetadataMsg) by adjusting the `@APIParam`
annotation on the private List<String> vmUuids field.
- Around line 29-33: The __example__ method in APICleanupVmInstanceMetadataMsg
uses fully qualified java.util.Arrays.asList; simplify by adding an import
(import java.util.Arrays;) at the top and replace java.util.Arrays.asList(...)
with Arrays.asList(...), or alternatively import java.util.List and use
List.of(...) to construct vmUuids more concisely; update the
APICleanupVmInstanceMetadataMsg.__example__ method to assign vmUuids using the
chosen simplified factory method.

In
`@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java`:
- Around line 18-20: APIUpdateVmInstanceMetadataMsg 中的 vmUuids 字段仅使用了
`@APIParam`(nonempty = true) 而未验证数据库中是否存在对应 VM 实例,建议在该注解上添加 checkExistence =
true;请定位类 APIUpdateVmInstanceMetadataMsg 中的私有字段 vmUuids 并将其 `@APIParam` 注解更新为包含
checkExistence = true(保留 resourceType = VmInstanceVO.class 和 nonempty = true),以在
API 层提前校验 VM UUID 的存在性。

In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`:
- Line 5: The field name vo in class ResourceMetadata is ambiguous; rename it to
a descriptive identifier (e.g., valueObject, voJson, or voSerialized depending
on its actual content) and update all related symbols: the private field, its
getter/setter methods (e.g., getVo()/setVo() ->
getValueObject()/setValueObject()), any constructors, builders, equals/hashCode,
toString, serialization annotations, and usages across the codebase to match the
new name to avoid breaking references; also update persistence/JSON mapping
annotations or column names if present so external contracts remain consistent.

In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java`:
- Around line 18-22: 将 VmMetadataErrors 中的可变字段 code 改为不可变,修改字段声明为 private final
String code,并在构造器 VmMetadataErrors(int id) 中按原有逻辑赋值(code =
String.format("VM_METADATA.%s", id));确保没有其它地方尝试重新赋值该字段(若有,改为使用新的常量或方法返回值替代)。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 7-9: 为接口 VmMetadataPathReplacementExtensionPoint 的两个方法添加 Javadoc:在
getPrimaryStorageType() 的注释中说明返回值含义(应返回哪个主存储类型标识、不能为 null 的约束等);在
calculatePathReplacements(String targetPsUuid, List<String> allOldPaths)
的注释中说明每个参数的含义(targetPsUuid 表示目标主存储 UUID,allOldPaths 为需替换的原始路径列表)、预期的返回值类型
PathReplacementResult
的语义(例如如何表示替换映射或无替换情况)、异常场景或线程安全/并发调用约束以及允许的空值处理策略;确保描述清晰以形成扩展点契约。

In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 9-24: The batchResolveVmUuids method uses an explicit loop and
null checks that can be simplified with Java Streams: replace the for-loop in
batchResolveVmUuids with a stream pipeline on fieldValues that filters out
nulls, maps each value via resolveVmUuid, filters out null results, and collects
to a List<String>; ensure you still return an empty list when fieldValues is
null or empty and keep the method signature and resolveVmUuid call unchanged.

In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 375-383: The simulator endpoint
LocalStorageSimulator.prefixRebaseBackingFiles currently constructs a
PrefixRebaseBackingFilesCmd but does not record it for test verification; update
prefixRebaseBackingFiles to add the received PrefixRebaseBackingFilesCmd (the
cmd variable) into the simulator's command log/config used by other endpoints
(same mechanism used by writeVmMetadata/scanVmMetadata) before replying with
PrefixRebaseBackingFilesRsp so tests can assert the command was invoked.

In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Line 37: The code treats trailing slashes inconsistently: resolveBaseDir
removes a trailing '/' while calculatePathReplacements derives newPrefix that
ensures a trailing '/', which is confusing; update the implementation to be
explicit by either (a) making both resolveBaseDir and newPrefix consistently
include or exclude the trailing slash, or (b) keep the current behavior but add
a clear comment in resolveBaseDir, calculatePathReplacements (and
buildMetadataDir) explaining the intended convention (e.g., resolveBaseDir
returns a no-trailing-slash base and other callers append '/' as needed),
referencing the resolveBaseDir, calculatePathReplacements, newPrefix and
buildMetadataDir symbols so future maintainers understand the design intent.
- Around line 29-38: Extract the duplicated logic that queries PrimaryStorageVO
for mountPath or url into a single helper (e.g., a private method like
fetchPrimaryStorageBase(String primaryStorageUuid) or getMountOrUrl) that uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() and falls back to selecting
PrimaryStorageVO_.url; have resolveBaseDir call this helper and throw the
CloudRuntimeException if it returns null, and have calculatePathReplacements
call the same helper and handle a null return by logging a warning and returning
null/empty as it currently does—this removes the duplicated query while
preserving each caller's error handling.

In
`@sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java`:
- Around line 6-11: The generated Result class
ScanVmInstanceMetadataFromPrimaryStorageResult uses a raw java.util.List for the
field vmInstanceMetadata and its accessor methods; change the field and the
signatures of getVmInstanceMetadata and setVmInstanceMetadata to use the generic
type java.util.List<VmMetadataScanEntry> (and add/import VmMetadataScanEntry as
needed) so callers get compile-time type safety and IDE completion; update the
SDK code generator that emits this class to preserve generic type parameters
from the source reply (ScanVmInstanceMetadataFromPrimaryStorageReply ->
List<VmMetadataScanEntry>) when emitting fields and getter/setter method
signatures.

In `@sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java`:
- Around line 83-85: The boolean getter is named getIncomplete which violates
JavaBean conventions; rename the method getIncomplete() to isIncomplete() in
class VmMetadataScanEntry and update all usages/tests/serializers that reference
getIncomplete to call isIncomplete instead; ensure the existing private boolean
field incomplete remains unchanged and, if necessary for serialization
compatibility, add a `@JsonProperty`("incomplete") annotation to the new
isIncomplete() or keep a delegating getIncomplete() that calls isIncomplete() to
preserve binary/JSON compatibility.

In `@testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy`:
- Around line 525-539: The four simulator handlers for
NfsPrimaryStorageKVMBackend (WRITE_VM_METADATA_PATH,
GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH, CLEANUP_VM_METADATA_PATH)
currently always return static success responses which prevents testing the
write-read-scan-cleanup metadata lifecycle; implement a minimal in-memory
metadata store (or VFS hook) inside the test harness and update the simulator
blocks to mutate/read that store: have the WRITE handler record metadata keyed
by vm/instance id, GET return stored metadata or not-found, SCAN enumerate
stored entries, and CLEANUP remove entries so the full chain can be validated
during tests.
🪄 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: c130435e-a80e-45b3-a777-90085b931d7b

📥 Commits

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

⛔ 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 (146)
  • 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/APIScanVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
  • header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
  • header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
  • header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
  • header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
  • header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
  • header/src/main/java/org/zstack/header/vm/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/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/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy
  • 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/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/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 +12 to +13
CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE,
CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `ManagementNodeVO` (`uuid`) ON DELETE SET NULL
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 只读核验:检查 upgrade 目录中 REFERENCES 的 schema 使用习惯
rg -n --type=sql 'REFERENCES\s+`[^`]+`' conf/db/upgrade
rg -n --type=sql 'REFERENCES\s+`zstack`\.`[^`]+`' conf/db/upgrade

Repository: MatheMatrix/zstack

Length of output: 50374


🏁 Script executed:

cat conf/db/upgrade/V5.0.0__schema.sql

Repository: MatheMatrix/zstack

Length of output: 1565


外键约束应显式包含 zstack schema 限定符以保持代码风格一致。

第 12-13 行和第 25 行的 FOREIGN KEY REFERENCES 子句缺少 zstack schema 限定符。虽然在当前 zstack 默认库上下文下仍能解析,但与同目录其他升级脚本的风格不一致,且在非 zstack 默认库上下文执行时可能引发解析风险。建议补全 schema 限定符。

建议修改
-    CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`)  REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE,
-    CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `ManagementNodeVO` (`uuid`) ON DELETE SET NULL
+    CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`)  REFERENCES `zstack`.`VmInstanceEO` (`uuid`) ON DELETE CASCADE,
+    CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `zstack`.`ManagementNodeVO` (`uuid`) ON DELETE SET NULL
@@
-    CONSTRAINT `fkVmMetadataFingerprintVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`)  REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE
+    CONSTRAINT `fkVmMetadataFingerprintVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`)  REFERENCES `zstack`.`VmInstanceEO` (`uuid`) ON DELETE CASCADE
📝 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
CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE,
CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `ManagementNodeVO` (`uuid`) ON DELETE SET NULL
CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `zstack`.`VmInstanceEO` (`uuid`) ON DELETE CASCADE,
CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `zstack`.`ManagementNodeVO` (`uuid`) ON DELETE SET NULL
Suggested change
CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE,
CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) REFERENCES `ManagementNodeVO` (`uuid`) ON DELETE SET NULL
CONSTRAINT `fkVmMetadataFingerprintVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `zstack`.`VmInstanceEO` (`uuid`) ON DELETE CASCADE
🤖 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 12 - 13, The FOREIGN KEY
REFERENCES clauses for the constraints fkVmMetadataDirtyVOVmInstanceEO and
fkVmMetadataDirtyVOManagementNodeVO (and the other FOREIGN KEY at the same
section) must explicitly qualify the referenced tables with the zstack schema;
update the REFERENCES targets from VmInstanceEO (`uuid`) and ManagementNodeVO
(`uuid`) (and the third referenced table at the same block) to
zstack.VmInstanceEO (`uuid`) and zstack.ManagementNodeVO (`uuid`) (and
zstack.<ThirdTable>) so the foreign key clauses consistently include the zstack
schema qualifier.

Comment on lines +108 to +112
response {
clz APIRegisterVmInstanceFromMetadataEvent.class
}
}
} No newline at end of file
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

文件末尾缺少换行符。

按照 POSIX 标准,文本文件应以换行符结尾。建议在第 112 行后添加一个空行。

🤖 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/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy`
around lines 108 - 112, Add a trailing newline at the end of the file so the
file ends with a newline character (POSIX compliance); locate the file block
that ends with the APIRegisterVmInstanceFromMetadataEvent.class response and the
closing braces and ensure there is a single blank line (newline) after the final
"}" so the file terminates with a newline.

Comment on lines +24 to +25
@Column
private long dirtyVersion;
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
# 只读核验:定位实体创建点及 dirtyVersion 显式赋值情况
rg -n --type=java '\bnew\s+VmMetadataDirtyVO\s*\('
rg -n --type=java '\.setDirtyVersion\s*\('

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

cat -n header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java

Repository: MatheMatrix/zstack

Length of output: 3718


🏁 Script executed:

# Search for database migrations or schema files related to VmMetadata
fd -e sql -e groovy | xargs rg -l "VmMetadata|vm_metadata" -i

Repository: MatheMatrix/zstack

Length of output: 479


🏁 Script executed:

# Look for Hibernate/JPA configuration or mapping files
fd -e xml -e properties | xargs rg -l "VmMetadataDirtyVO|vm_metadata" -i

Repository: MatheMatrix/zstack

Length of output: 82


🏁 Script executed:

# Check the database schema for vm_metadata_dirty table
rg -A 20 "vm_metadata_dirty" conf/db/upgrade/V5.0.0__schema.sql conf/db/upgrade/V4.10.16__schema.sql

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# Read persistence.xml to understand JPA configuration
cat -n conf/persistence.xml | head -100

Repository: MatheMatrix/zstack

Length of output: 7477


🏁 Script executed:

# Search for VmMetadataDirtyVO in persistence.xml
rg "VmMetadataDirtyVO" conf/persistence.xml

Repository: MatheMatrix/zstack

Length of output: 132


🏁 Script executed:

# Search for any mention of metadata_dirty in schema files
rg -i "metadata" conf/db/upgrade/*.sql | head -50

Repository: MatheMatrix/zstack

Length of output: 6613


🏁 Script executed:

# Look for Groovy schema definitions
fd -e groovy "schema|migration" | xargs rg -l "metadata_dirty|VmMetadata" -i

Repository: MatheMatrix/zstack

Length of output: 46


🏁 Script executed:

# Get the complete CREATE TABLE statement for VmMetadataDirtyVO
rg -A 30 "CREATE TABLE IF NOT EXISTS.*VmMetadataDirtyVO" conf/db/upgrade/V5.0.0__schema.sql

Repository: MatheMatrix/zstack

Length of output: 1565


🏁 Script executed:

# Also check V4.10.16 version
rg -A 30 "VmMetadataDirtyVO" conf/db/upgrade/V4.10.16__schema.sql

Repository: MatheMatrix/zstack

Length of output: 44


初始化 dirtyVersion 为 1,避免 JPA 持久化时与数据库默认值不一致。

当前 dirtyVersion 为未初始化的 primitive long 类型。在 Java 中,primitive 类型的默认值为 0,JPA 在持久化时会显式写入 0 值,而不是依赖数据库的 DEFAULT 1。这导致实体层的初始值与数据库定义的默认值不一致。

建议在实体层初始化为 1L:

修改建议
-    private long dirtyVersion;
+    private long dirtyVersion = 1L;
📝 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
@Column
private long dirtyVersion;
`@Column`
private long dirtyVersion = 1L;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`
around lines 24 - 25, The dirtyVersion field in VmMetadataDirtyVO is currently
an uninitialized primitive long (defaults to 0) which causes JPA to persist 0
instead of the DB default 1; initialize dirtyVersion to 1L at the entity level
(either via a field initializer or in the VmMetadataDirtyVO constructor) so the
value matches the database default and JPA will persist 1 instead of 0; update
the declaration of dirtyVersion and/or the class constructor accordingly.

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

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证 VolumeUuidToVmUuidResolver 实现是否存在
rg -n "VolumeUuidToVmUuidResolver" --type java

Repository: MatheMatrix/zstack

Length of output: 2491


🏁 Script executed:

#!/bin/bash
# Search for the class definition of VolumeUuidToVmUuidResolver
rg "class VolumeUuidToVmUuidResolver" --type java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for resolver implementations to understand the pattern
rg "Resolver.*class|implements.*Resolver" --type java | head -20

Repository: MatheMatrix/zstack

Length of output: 260


🏁 Script executed:

#!/bin/bash
# Search for MetadataImpact class definition
rg "class MetadataImpact|interface MetadataImpact" --type java -A 10

Repository: MatheMatrix/zstack

Length of output: 1199


🏁 Script executed:

#!/bin/bash
# Search for VmUuidFromApiResolver to understand the interface
rg "VmUuidFromApiResolver|VolumeUuidToVmUuidResolver" --type java -A 5 -B 2

Repository: MatheMatrix/zstack

Length of output: 14871


🏁 Script executed:

#!/bin/bash
# Search for bean configuration or component definition of VolumeUuidToVmUuidResolver
rg "@Bean.*VolumeUuidToVmUuidResolver|@Component.*VolumeUuidToVmUuidResolver|new VolumeUuidToVmUuidResolver|implements.*VmUuidFromApiResolver" --type java -B 3 -A 3

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for any file containing VolumeUuidToVmUuidResolver
fd -type f | xargs rg -l "VolumeUuidToVmUuidResolver" 2>/dev/null | head -20

Repository: MatheMatrix/zstack

Length of output: 1184


🏁 Script executed:

#!/bin/bash
# Search for other resolver implementations
rg "class.*Resolver.*implements.*VmUuidFromApiResolver" --type java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for Spring XML configuration files
find . -name "*.xml" -type f | grep -i spring | head -10

Repository: MatheMatrix/zstack

Length of output: 565


🏁 Script executed:

#!/bin/bash
# Search for VolumeUuidToVmUuidResolver in XML files
rg "VolumeUuidToVmUuidResolver" --type xml

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for any *Resolver classes in storage/volume modules
find . -path "*/storage/*" -o -path "*/volume/*" | xargs rg "class.*Resolver" --type java 2>/dev/null | grep -v "test" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check the full APIDeleteDataVolumeMsg.java file
cat header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java

Repository: MatheMatrix/zstack

Length of output: 2127


缺少 VolumeUuidToVmUuidResolver 实现,无法进行 Spring 装配

@MetadataImpact 注解引用的 Spring bean VolumeUuidToVmUuidResolver 在代码库中不存在。该 resolver 需要实现 VmUuidFromApiResolver 接口,否则运行时将抛出 NoSuchBeanDefinitionException。请实现该 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/volume/APIDeleteDataVolumeMsg.java` at
line 7, The project references a Spring bean named VolumeUuidToVmUuidResolver
via the `@MetadataImpact` annotation but that bean is missing; implement a class
VolumeUuidToVmUuidResolver that implements the VmUuidFromApiResolver interface
(or change the `@MetadataImpact` usage to point to an existing resolver) so Spring
can autowire it at startup; ensure the new class lives on the component-scan
path, is annotated appropriately (e.g., `@Component` or registered as a bean),
implements the required methods from VmUuidFromApiResolver, and is referenced by
APIDeleteDataVolumeMsg's `@MetadataImpact` configuration.

Comment on lines +3471 to +3490
new While<>(connectedHostUuids).all((hostUuid, com) -> {
LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {
@Override
public void success(ScanVmInstanceMetadataFromPrimaryStorageReply returnValue) {
if (returnValue.getVmInstanceMetadata() != null) {
allSummaries.addAll(returnValue.getVmInstanceMetadata());
}
com.done();
}

@Override
public void fail(ErrorCode errorCode) {
logger.warn(String.format("failed to scan vm metadata from host[uuid:%s] on local primary storage[uuid:%s]: %s",
hostUuid, self.getUuid(), errorCode));
com.addError(errorCode);
com.done();
}
});
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

把同步 host 解析失败也收敛到单机失败路径。

这里在 bkd.handle(...) 之前同步执行 getHypervisorBackendFactoryByHostUuid() / getHypervisorBackend()。如果 host 在查询后被并发 detach/delete,这里会直接抛异常,绕过 com.addError(...); com.done(),把当前方法的“部分失败继续扫描”退化成整次失败。建议在这两步外层补 try/catch,按单 host 记错后继续。

🤖 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 - 3490, The calls to
getHypervisorBackendFactoryByHostUuid(...) and f.getHypervisorBackend(self) can
throw if the host is detached concurrently, which bypasses the per-host error
handling; wrap the lines that obtain LocalStorageHypervisorFactory and
LocalStorageHypervisorBackend (the calls to
getHypervisorBackendFactoryByHostUuid(...) and f.getHypervisorBackend(self)) in
a try/catch inside the While callback, and on exception invoke com.addError(...)
with a converted ErrorCode (or build one), call com.done(), and log the failure,
then return so bkd.handle(...) is not called for that host; keep existing
success/fail behavior in bkd.handle(...) unchanged.

@zstack-robot-1
Copy link
Copy Markdown
Collaborator

Comment from yaohua.wu:

Review: MR !9494 — ZSV-11559 <fix>[vm]: add MetadataImpact

跨仓 Review:本次 Review 覆盖 ZSV-11559 的三个 MR(zstack !9494 / premium !13363 / zstack-utility !6832),每个 MR 上均有独立评论。

变更概述:引入 VM Metadata 子系统,为「注册虚拟机」功能持久化 VM 配置到主存储。核心包括:

  • @MetadataImpact 注解框架(~37 个 API 消息标注)
  • VmMetadataDirtyVO / VmMetadataFingerprintVO 脏跟踪基础设施
  • 5 个新 API(Register/Scan/Get/Update/Cleanup VM Metadata)
  • VmExpungeMetadataFlow 清理流程 + GC 重试
  • 6 个扩展点接口 + 12 个 GlobalConfig

🟡 Warning

1. [APIMigrateVmMsg.java] @MetadataImpact 缺少 updateOnFailure = true

迁移失败可能导致存储处于部分迁移状态(如 root volume 已迁到目标 host/pool),此时元数据仍指向旧位置。APIRevertVolumeFromSnapshotMsgAPIDeleteVolumeSnapshotMsg 等同为 STORAGE 影响的操作已正确设置 updateOnFailure = trueAPIMigrateVmMsg 应保持一致。

建议:添加 updateOnFailure = true

2. [APIReimageVmInstanceMsg.java] @MetadataImpact 缺少 updateOnFailure = true

Reimage 替换 root volume,失败可能留下部分替换状态,与迁移同理。

建议:添加 updateOnFailure = true

3. [APIScanVmInstanceMetadataFromPrimaryStorageMsg.java] GET 方法返回 APIEvent — REST 语义不一致

REST 映射使用 HttpMethod.GETresponseClassAPIEvent。ZStack REST 框架中,GET 通常对应同步查询(返回 APIReply),异步操作应使用 POST。Scan 操作涉及主存储 I/O,本质是异步操作。

建议:改为 method = HttpMethod.POST

4. [APIGetVmInstanceMetadataFromPrimaryStorageMsg.java] 同上 — GET + APIEvent 不一致

从主存储读取元数据涉及后端 I/O 并返回 APIEvent,应使用 POST 或改为同步 APIReply

5. [VmInstanceMetadataConstants.java] extractOldPrefix 仅硬编码 4 种路径标记

STORAGE_PATH_MARKERS 只包含 /rootVolumes//dataVolumes//volumeSnapshots//memory/,未来新增路径类型时会静默返回 null。SharedBlock 的 sharedblock:// scheme 路径也会返回 null

建议:路径解析失败时添加 warn 日志而非静默返回 null,便于问题诊断。

6. [APIShrinkVolumeSnapshotMsg.java] @MetadataImpact 可能需要 updateOnFailure = true

Shrink 是存储变异操作,部分成功失败后可能留下已变更的 backing files,但当前未设置 updateOnFailureAPIDeleteVolumeSnapshotMsg 已设置,此处应评估是否需要一致。

🟢 Suggestion

1. [VmMetadataDirtyVO.java / VmMetadataFingerprintVO.java] @Table 缺少显式 name

JPA 从类名推导表名,当前与 SQL schema 匹配但较脆弱。建议显式声明 @Table(name = "VmMetadataDirtyVO")

2. [VmMetadataScanEntryDoc_zh_cn.groovy] 文档 Groovy 文件为空 stub

其他 *Doc_zh_cn.groovy 均有字段文档,此文件遗漏。

3. [ResourceMetadata.java] vo/systemTags/resourceConfigs 字段缺少 Javadoc

这些 String 字段实际存储序列化 JSON,应添加 Javadoc 说明格式约定。

4. 多处文件缺少末尾换行

VmInstanceMetadataConstants.javaAPICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyprimaryStorage.xml 末尾缺少换行符。

亮点

  • @MetadataImpact 注解 + Resolver 模式设计清晰,声明式标注 + 策略解析解耦良好
  • VmExpungeMetadataFlow 的 GC 重试机制完善,正确检查 PS/Host 存在性
  • 防御性空值处理充分(spec/inventory/psUuid/psType/extensionPoint 均有检查)
  • 12 个 GlobalConfig 覆盖全面(enable/concurrency/poll/batch/retry/zombie/drift/stale/payload)

Verdict: APPROVED(有建议)

Warning 1-2(updateOnFailure)建议在合并前修复,其余为改进建议。


🤖 Robot Reviewer

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.

3 participants