Skip to content

<feature>[vm_local_volume_cache]: vm local volume cache imp#3406

Open
zstack-robot-2 wants to merge 1 commit intofeature-5.5.6-local-cachefrom
sync/haidong.pang/feature/ZSTAC-81715
Open

<feature>[vm_local_volume_cache]: vm local volume cache imp#3406
zstack-robot-2 wants to merge 1 commit intofeature-5.5.6-local-cachefrom
sync/haidong.pang/feature/ZSTAC-81715

Conversation

@zstack-robot-2
Copy link
Collaborator

Related: ZSTAC-81715

Change-Id: I6d747a786b73746a6f756a6c72787479726e6765

sync from gitlab !9255

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

演练

此拉取请求向 ZStack 引入了虚拟机本地卷缓存(Local Volume Cache)功能,包括数据库表、API 消息事件、虚拟机创建配置扩展、卷管理集成、KVM 代理命令和库存类,支持在主机上缓存虚拟机卷。

变更

队列/文件 摘要
数据库架构
conf/db/upgrade/V5.5.12__schema.sql
添加三个新表:VmLocalVolumeCachePoolVO(缓存池元数据)、VmLocalVolumeCachePoolCapacityVO(容量跟踪)和VmLocalVolumeCacheVO(卷缓存映射),包含外键约束和级联删除。
API 消息和事件
header/src/main/java/org/zstack/header/host/APIGetBlockDevices*GetBlockDevicesOn*HostBlockDeviceStruct.java
新增 API 端点用于获取主机块设备,包括消息类、事件类和数据结构,以及相应的处理实现。
虚拟机创建配置扩展
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.javaCreateVmInstanceMsg.javaInstantiateNewCreatedVmInstanceMsg.javaVmInstanceSpec.java
添加根卷和数据卷缓存配置字段(enableRootVolumeCachecacheModerootVolumeCachePoolUuidrootVolumeCacheModedataDiskCacheConfigOnIndex)以及相应的访问器。
虚拟机管理流程
compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.javaVmInstanceBase.javaVmInstanceManagerImpl.javaVmInstanceUtils.javaVmAllocateHostFlow.javaVmAllocateVolumeFlow.javaVmInstantiateOtherDiskFlow.java
将缓存配置通过虚拟机创建流程传播,实现主机分配时的缓存约束标签添加、卷缓存初始化和配置映射。
主机处理
compute/src/main/java/org/zstack/compute/host/HostBase.java
添加 APIGetBlockDevicesMsg 消息处理器,路由到内部消息获取块设备列表。
库存和值对象类
header/src/main/java/org/zstack/header/localVolumeCache/*
新增缓存相关库存类和 VO:VmLocalVolumeCacheInventoryVmLocalVolumeCachePoolInventoryVmLocalVolumeCachePoolCapacityInventory 及其对应 VO 类、枚举类(状态、模式)和元数据类。
卷管理集成
header/src/main/java/org/zstack/header/volume/VolumeAO.javaVolumeInventory.javastorage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.javaVolumeSystemTags.java
为卷添加本地缓存关联、缓存系统标签(VOLUME_CACHE_ENABLEVOLUME_CACHE_POOL_UUIDVOLUME_CACHE_MODE)和卷创建时的缓存记录初始化。
KVM 代理支持
plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.javaKVMConstant.javaKVMHost.javaVolumeTO.javaCacheTO.java
添加缓存池和缓存操作命令、块设备查询命令、缓存相关常量和卷数据传输对象扩展,支持缓存元数据映射。
API 拦截和错误代码
plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.javautils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.javastorage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
微小格式调整和新增 124 个错误代码常量覆盖多个子系统(存储、网络、计算等)。
文档和示例
header/src/main/java/org/zstack/header/.../...*Doc_zh_cn.groovy
添加中文文档文件,描述新 API、库存类、枚举和数据结构。

序列图

sequenceDiagram
    actor User
    participant API as APIGateway
    participant VMManager as VmInstanceManager
    participant VolMgr as VolumeManager
    participant HostMgr as HostBase
    participant DB as Database
    participant KVMAgent as KVM Agent

    User->>API: APICreateVmInstanceMsg<br/>(enableRootVolumeCache=true)
    API->>VMManager: CreateVmInstanceMsg<br/>(缓存配置传播)
    VMManager->>VolMgr: VmAllocateVolumeFlow<br/>(应用缓存配置)
    VolMgr->>DB: 创建 VmLocalVolumeCacheVO<br/>(初始状态)
    VolMgr->>VolMgr: 添加缓存系统标签<br/>volumeCache::*
    VMManager->>HostMgr: AllocateHostMsg<br/>(requireSize 标签)
    HostMgr->>DB: 查询主机容量
    HostMgr->>VMManager: 主机已分配
    VMManager->>KVMAgent: CreateVmCmd<br/>(VolumeTO 含 CacheTO)
    KVMAgent->>KVMAgent: 初始化缓存池<br/>附加卷
    KVMAgent->>VMManager: VM 创建完成
    VMManager->>User: VM 已创建<br/>(缓存已启用)
Loading

预估代码审查工作量

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

🐰 缓存之梦在云端飘零,
本地卷舞动着数据的韵律,
主机池容量汇聚成河,
APIGetBlockDevices 细数磁盘故事,
分层递进,缓存绽放光芒!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题遵循要求的格式 [scope]: ,长度为59个字符,在72字符限制内。
Description check ✅ Passed 描述与变更集相关,提及ZSTAC-81715和GitLab同步,描述了VM本地卷缓存功能的实现。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sync/haidong.pang/feature/ZSTAC-81715
📝 Coding Plan for PR comments
  • Generate coding plan

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.41.0)
compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 17

🧹 Nitpick comments (12)
plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolMessage.java (1)

3-5: 建议添加 Javadoc 注释以说明接口用途。

VmLocalVolumeCacheMessage 接口相比,此接口缺少文档注释。根据编码规范,接口方法应配有有效的 Javadoc 注释。

📝 建议的修改
 package org.zstack.kvm.localvolumecache;
 
+/**
+ * Marker interface for messages routed to VmLocalVolumeCachePoolBase via passThrough.
+ */
 public interface VmLocalVolumeCachePoolMessage {
     String getPoolUuid();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolMessage.java`
around lines 3 - 5, 在 VmLocalVolumeCachePoolMessage 接口上添加 Javadoc
注释以说明接口用途和方法含义;具体在接口声明处为 VmLocalVolumeCachePoolMessage 添加一段简短描述(功能、使用场景),并为其方法
getPoolUuid() 添加 `@return` 描述说明返回值含义,必要时参考相似接口 VmLocalVolumeCacheMessage
的注释风格以保持一致性,并确保注释遵循项目的 Javadoc 规范。
compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java (1)

415-417: 建议补充透传 cache pool/mode,避免缓存配置在此链路被忽略。

当前 EnableVolumeCacheMsg 仅设置了 volumeUuid。若上游已携带池与模式配置,这里未写入会导致策略退化为默认行为。

♻️ 可选改进示例
 EnableVolumeCacheMsg emsg = new EnableVolumeCacheMsg();
 emsg.setVolumeUuid(volumeInventory.getUuid());
+if (diskAO.getCachePoolUuid() != null) {
+    emsg.setPoolUuid(diskAO.getCachePoolUuid());
+}
+if (diskAO.getCacheMode() != null) {
+    emsg.setCacheMode(diskAO.getCacheMode());
+}
 bus.makeLocalServiceId(emsg, VolumeConstant.SERVICE_ID);
🤖 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/VmInstantiateOtherDiskFlow.java`
around lines 415 - 417, EnableVolumeCacheMsg 目前只设置了 volumeUuid(在
EnableVolumeCacheMsg 和 bus.makeLocalServiceId(..., VolumeConstant.SERVICE_ID)
处),会丢失上游传来的缓存池/模式配置;请在构造 EnableVolumeCacheMsg 时把上游的 cache pool 与 cache mode
字段透传到该消息(例如从调用上下文或 volumeInventory/请求对象中读取对应的缓存配置字段并通过 EnableVolumeCacheMsg 的对应
setter 赋值),确保发送前包含 cache pool 与 cache mode,保持原有 bus.makeLocalServiceId 调用不变。
plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java (1)

68-90: 拷贝构造函数浅拷贝 cache 的潜在风险较低。

Line 89 的 this.cache = other.cache; 确实存在浅拷贝问题。但由于 CacheTO 的所有字段均为 String 类型(不可变),即使两个 VolumeTO 共享同一个 CacheTO 引用,通过 setter 修改的也都是不可变的 String 对象,不会产生实际的状态污染。此外,代码中未发现 .getCache().setXxx() 这样的修改模式,该风险在实际应用中未被触发。

若要从代码规范角度完整修复,可考虑深拷贝(对应字段逐个复制)或使用不可变对象设计,但当前实际影响有限。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java` around lines 68 - 90,
The copy constructor VolumeTO(VolumeTO other) currently performs a shallow copy
of the cache field (this.cache = other.cache), which can lead to
shared-reference issues; change it to perform a deep copy of CacheTO by
constructing a new CacheTO instance and copying its String fields (e.g., via new
CacheTO(other.getCache()) or by setting each field from other.getCache()) so
VolumeTO and other do not share the same CacheTO reference; update the
constructor to use the new CacheTO copy and ensure getters/setters used are
CacheTO-specific (getCache(), setCache(...)).
plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolVO.java (1)

43-47: 容量字段建议使用 long 类型

totalCapacityavailableCapacity 使用 double 类型可能导致浮点精度问题。存储容量通常以字节为单位,建议使用 long 类型以避免精度损失,这也是存储系统中的常见做法。

♻️ 建议修改
     `@Column`
-    private double totalCapacity;
+    private long totalCapacity;
 
     `@Column`
-    private double availableCapacity;
+    private long availableCapacity;

同时需要更新相应的 getter/setter 方法:

-    public double getTotalCapacity() {
+    public long getTotalCapacity() {
         return totalCapacity;
     }
 
-    public void setTotalCapacity(double totalCapacity) {
+    public void setTotalCapacity(long totalCapacity) {
         this.totalCapacity = totalCapacity;
     }
 
-    public double getAvailableCapacity() {
+    public long getAvailableCapacity() {
         return availableCapacity;
     }
 
-    public void setAvailableCapacity(double availableCapacity) {
+    public void setAvailableCapacity(long availableCapacity) {
         this.availableCapacity = availableCapacity;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolVO.java`
around lines 43 - 47, The two capacity fields totalCapacity and
availableCapacity in VmLocalVolumeCachePoolVO are typed as double and should be
changed to long to avoid floating-point precision issues for byte-sized storage;
update the field declarations in class VmLocalVolumeCachePoolVO, change their
types to long, and adjust the corresponding getter/setter methods
(getTotalCapacity/setTotalCapacity and
getAvailableCapacity/setAvailableCapacity) to use long; also check any places
that read/write these fields (ORM mapping annotations on the fields and any code
that expects double) and update conversions or database column type mappings if
necessary to keep persistence consistent.
plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CachePoolHostAllocatorFilter.java (2)

69-71: 简化返回空列表的写法

使用 filter(h -> false) 来返回空列表是不必要的复杂写法。

♻️ 建议修改
-                return candidates.stream()
-                        .filter(h -> false)
-                        .collect(Collectors.toList());
+                return Collections.emptyList();

需要添加导入:

import java.util.Collections;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CachePoolHostAllocatorFilter.java`
around lines 69 - 71, The code in CachePoolHostAllocatorFilter returns an empty
list via candidates.stream().filter(h -> false).collect(...), which is
unnecessarily complex; replace that expression with Collections.emptyList() and
add the import java.util.Collections; update the return in the relevant method
inside class CachePoolHostAllocatorFilter to return Collections.emptyList()
instead of using the stream/filter pattern.

93-94: 简化 Set 转换

hostsWithPool 已经是 List<String>,可以直接使用 new HashSet<>(hostsWithPool) 或者使用 Set.copyOf() (Java 10+),无需再次 stream。

♻️ 建议修改
-        Set<String> qualifiedHostUuids = hostsWithPool.stream().collect(Collectors.toSet());
+        Set<String> qualifiedHostUuids = new HashSet<>(hostsWithPool);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CachePoolHostAllocatorFilter.java`
around lines 93 - 94, The code creates qualifiedHostUuids by streaming
hostsWithPool which is already a List<String>; replace the stream-based
conversion with a direct Set construction to simplify and improve readability:
in CachePoolHostAllocatorFilter replace the
hostsWithPool.stream().collect(Collectors.toSet()) expression used to assign
qualifiedHostUuids with either new HashSet<>(hostsWithPool) or
Set.copyOf(hostsWithPool) (Java 10+) so qualifiedHostUuids is built directly
from hostsWithPool.
compute/src/main/java/org/zstack/compute/vm/VmInstantiateCacheFlow.java (1)

54-63: 考虑使用批量查询优化性能

当前实现在循环中为每个 volume UUID 执行单独的数据库查询。当有多个数据卷时,这会产生 N+1 查询问题。建议使用批量查询。

♻️ 建议修改
-        List<VmLocalVolumeCacheVO> toInstantiate = new ArrayList<>();
-        for (String volUuid : volumeUuids) {
-            VmLocalVolumeCacheVO cacheVO = Q.New(VmLocalVolumeCacheVO.class)
-                    .eq(VmLocalVolumeCacheVO_.volumeUuid, volUuid)
-                    .find();
-            if (cacheVO != null
-                    && cacheVO.getState() == VmLocalVolumeCacheState.Uninstantiated
-                    && cacheVO.getPoolUuid() != null) {
-                toInstantiate.add(cacheVO);
-            }
-        }
+        List<VmLocalVolumeCacheVO> toInstantiate = Q.New(VmLocalVolumeCacheVO.class)
+                .in(VmLocalVolumeCacheVO_.volumeUuid, volumeUuids)
+                .eq(VmLocalVolumeCacheVO_.state, VmLocalVolumeCacheState.Uninstantiated)
+                .notNull(VmLocalVolumeCacheVO_.poolUuid)
+                .list();
🤖 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/VmInstantiateCacheFlow.java`
around lines 54 - 63, The loop in VmInstantiateCacheFlow issues one DB query per
volumeUuid causing N+1 queries; replace it with a single batch query against
VmLocalVolumeCacheVO using an IN filter on volumeUuid plus conditions for state
== VmLocalVolumeCacheState.Uninstantiated and poolUuid != null, then iterate the
returned list to populate toInstantiate (matching by volumeUuid or simply adding
all results); update references to volumeUuids, VmLocalVolumeCacheVO,
VmLocalVolumeCacheState and toInstantiate accordingly to remove per-UUID Q.New
calls.
storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java (2)

694-708: 建议对 volumeUuid 进行 trim 处理

根据编码规范,API 参数可能包含用户从浏览器复制粘贴时带入的空格或换行符。建议对 volumeUuid 进行 trim 处理。

♻️ 建议修改
     private void validate(APIEnableVolumeCacheMsg msg) {
-        String volumeUuid = msg.getVolumeUuid();
+        String volumeUuid = msg.getVolumeUuid();
+        if (volumeUuid != null) {
+            volumeUuid = volumeUuid.trim();
+            msg.setVolumeUuid(volumeUuid);
+        }
         if (!dbf.isExist(volumeUuid, VolumeVO.class)) {
             throw new ApiMessageInterceptionException(argerr("volume[uuid:%s] does not exist", volumeUuid));
         }

As per coding guidelines: "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"

🤖 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/volume/VolumeApiInterceptor.java`
around lines 694 - 708, In validate(APIEnableVolumeCacheMsg msg) trim the
incoming volumeUuid (e.g., String volumeUuid = msg.getVolumeUuid().trim())
before using it in dbf.isExist(...) and the Q.New(...).eq(...) lookup and in
exception messages; ensure you handle null safely (check msg.getVolumeUuid() for
null) so the validation uses the trimmed value for existence checks and
comparisons (refer to validate, volumeUuid, dbf.isExist,
Q.New(VmLocalVolumeCacheVO.class) and VmLocalVolumeCacheVO_.volumeUuid).

710-723: 同样建议对 APIDisableVolumeCacheMsg 的 volumeUuid 进行 trim 处理

validate(APIEnableVolumeCacheMsg) 相同的问题。

♻️ 建议修改
     private void validate(APIDisableVolumeCacheMsg msg) {
-        String volumeUuid = msg.getVolumeUuid();
+        String volumeUuid = msg.getVolumeUuid();
+        if (volumeUuid != null) {
+            volumeUuid = volumeUuid.trim();
+            msg.setVolumeUuid(volumeUuid);
+        }
         if (!dbf.isExist(volumeUuid, VolumeVO.class)) {
             throw new ApiMessageInterceptionException(argerr("volume[uuid:%s] does not exist", volumeUuid));
         }

As per coding guidelines: "注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等"

🤖 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/volume/VolumeApiInterceptor.java`
around lines 710 - 723, The validate(APIDisableVolumeCacheMsg msg) method uses
volumeUuid directly; trim the input like in validate(APIEnableVolumeCacheMsg) by
obtaining a trimmed variable (e.g., String volumeUuid = msg.getVolumeUuid() ==
null ? null : msg.getVolumeUuid().trim()), validate it for null/empty before
calling dbf.isExist(…, VolumeVO.class), and then use that trimmed variable in
the Q.New(VmLocalVolumeCacheVO.class).eq(VmLocalVolumeCacheVO_.volumeUuid,
volumeUuid).find() and subsequent checks against VmLocalVolumeCacheState.None to
ensure whitespace from APIDisableVolumeCacheMsg is handled.
plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIExtendVmLocalVolumeCachePoolEvent.java (1)

25-27: __example__() 方法应填充示例 inventory

参考 APICreateVmLocalVolumeCachePoolEvent.__example__() 的实现,该方法设置了 inventory 示例数据。为保持一致性并生成更完整的 API 文档,建议在此方法中也设置 inventory。

♻️ 建议修改
     public static APIExtendVmLocalVolumeCachePoolEvent __example__() {
-        return new APIExtendVmLocalVolumeCachePoolEvent();
+        APIExtendVmLocalVolumeCachePoolEvent event = new APIExtendVmLocalVolumeCachePoolEvent();
+        VmLocalVolumeCachePoolInventory inventory = new VmLocalVolumeCachePoolInventory();
+        event.setInventory(inventory);
+        return event;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIExtendVmLocalVolumeCachePoolEvent.java`
around lines 25 - 27, The __example__() in class
APIExtendVmLocalVolumeCachePoolEvent currently returns an empty event; update
APIExtendVmLocalVolumeCachePoolEvent.__example__ to construct and return an
event with a populated inventory (mirroring
APICreateVmLocalVolumeCachePoolEvent.__example__), e.g., create a
VmLocalVolumeCachePoolInventory instance, fill its fields with representative
sample values (uuid, name, description, type, url, zoneUuid, primaryStorageUuid,
createDate, lastOpDate, etc.), call event.setInventory(populatedInventory), and
then return the event so the API docs include a concrete inventory example.
storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java (1)

1516-1518: 使用 dbf.getCurrentSqlTime() 替代 new java.util.Date()

直接使用 new java.util.Date().getTime() 创建时间戳可能导致时间不一致问题,特别是在分布式环境中。建议使用 dbf.getCurrentSqlTime() 以确保与数据库时间同步。

♻️ 建议修改
-            cacheVO.setCreateDate(new Timestamp(new java.util.Date().getTime()));
-            cacheVO.setLastOpDate(new Timestamp(new java.util.Date().getTime()));
+            Timestamp now = dbf.getCurrentSqlTime();
+            cacheVO.setCreateDate(now);
+            cacheVO.setLastOpDate(now);
🤖 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/volume/VolumeManagerImpl.java`
around lines 1516 - 1518, Replace the two Timestamp constructions using new
java.util.Date().getTime() for cacheVO timestamps with the database-synchronized
time from dbf.getCurrentSqlTime(); in VolumeManagerImpl where
cacheVO.setCreateDate(...) and cacheVO.setLastOpDate(...) are called (just
before dbf.persist(cacheVO)), call new
Timestamp(dbf.getCurrentSqlTime().getTime()) or otherwise use
dbf.getCurrentSqlTime() directly to set both create and last-op dates so the
persisted timestamps are consistent with DB time.
header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java (1)

412-421: 建议在新字符串字段写入前做 trim 规整

Line 412 和 Line 420 直接保存原始字符串,来自消息链路时若包含前后空格/换行,后续按 UUID 或 mode 匹配时容易出现隐式失败。建议在 setter 中做 trim()(可选再把空串归一化为 null)。

💡 建议修改
 public void setCachePoolUuid(String cachePoolUuid) {
-    this.cachePoolUuid = cachePoolUuid;
+    if (cachePoolUuid == null) {
+        this.cachePoolUuid = null;
+        return;
+    }
+    String trimmed = cachePoolUuid.trim();
+    this.cachePoolUuid = trimmed.isEmpty() ? null : trimmed;
 }
 
 public void setCacheMode(String cacheMode) {
-    this.cacheMode = cacheMode;
+    if (cacheMode == null) {
+        this.cacheMode = null;
+        return;
+    }
+    String trimmed = cacheMode.trim();
+    this.cacheMode = trimmed.isEmpty() ? null : trimmed;
 }

As per coding guidelines “注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等”。

🤖 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/CreateVmInstanceMsg.java` around
lines 412 - 421, The setters setCachePoolUuid and setCacheMode in
CreateVmInstanceMsg currently assign raw input strings; update both to trim
incoming values (e.g., String v = input == null ? null : input.trim()) and
normalize empty strings to null before assigning to this.cachePoolUuid and
this.cacheMode so leading/trailing whitespace or newlines from messages won’t
break UUID/mode matching.
🤖 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/VmAllocateHostFlow.java`:
- Around line 154-161: The root-cache-size calculation currently uses
image.getSize() whenever an ImageInventory exists, which treats ISOs as having
cache size and mismatches the prepareMsg logic; update the block in
VmAllocateHostFlow where enableRoot is handled so that you only use
image.getSize() when the image is not an ISO and size != 0, otherwise fall back
to spec.getRootDiskOffering().getDiskSize() (i.e., change the condition around
ImageInventory image = spec.getImageSpec().getInventory() to check image media
type/ISO status before using image.getSize()); keep totalCacheSize accumulation
and ensure this logic mirrors the root-size calculation in prepareMsg.

In `@conf/db/upgrade/V5.5.12__schema.sql`:
- Around line 7-8: The capacity columns `totalCapacity` and `availableCapacity`
are currently defined as DOUBLE; change their types to BIGINT UNSIGNED to avoid
floating-point precision issues and match prior schema versions (use
`totalCapacity` BIGINT UNSIGNED NOT NULL DEFAULT 0 and `availableCapacity`
BIGINT UNSIGNED NOT NULL DEFAULT 0). Update the ALTER/CREATE statement in
V5.5.12__schema.sql to use BIGINT UNSIGNED and ensure any migration path handles
existing rows (e.g., cast/validate current values to integers and apply DEFAULT
0 for NULLs) so upgrades don’t lose or corrupt capacity data.

In `@header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java`:
- Around line 22-34: The equals/hashCode pair in HostBlockDeviceStruct currently
treats all instances with wwid == null as equal; change the logic in
equals(Object o) and hashCode() so that if wwid is non-null you compare/hash by
wwid, but if wwid is null you fall back to a stable secondary key composed of
path and hctl (e.g., compare path.equals(...) and hctl.equals(...) and compute
hash from path and hctl). Update both HostBlockDeviceStruct.equals and
HostBlockDeviceStruct.hashCode to use the same wwid-first else path+hctl
strategy so null-wwid devices are distinguished consistently.

In
`@header/src/main/java/org/zstack/header/volume/localcache/APIDisableVolumeCacheEvent.java`:
- Around line 5-6: APIDisableVolumeCacheEvent is missing the `@RestResponse`
annotation for its inventory field; update the class APIDisableVolumeCacheEvent
to add the `@RestResponse`(allTo = "inventory") annotation (same as
APIEnableVolumeCacheEvent) so the VmLocalVolumeCacheInventory inventory is
exposed correctly in REST responses.

In
`@header/src/main/java/org/zstack/header/volume/localcache/APIEnableVolumeCacheEvent.java`:
- Around line 5-6: The APIEnableVolumeCacheEvent class declares a
VmLocalVolumeCacheInventory field named inventory but is missing the REST
response annotation; add the annotation `@RestResponse`(allTo = "inventory") to
the APIEnableVolumeCacheEvent class (same pattern used in
APIExtendVmLocalVolumeCachePoolEvent and APIUpdateVmLocalVolumeCachePoolEvent)
so the inventory is correctly serialized in REST responses.

In
`@header/src/main/java/org/zstack/header/volume/localcache/APIEnableVolumeCacheMsg.java`:
- Around line 29-31: In APIEnableVolumeCacheMsg update the setVolumeUuid(String
volumeUuid) setter to trim incoming values before assignment (e.g., assign
this.volumeUuid = volumeUuid == null ? null : volumeUuid.trim()) so pasted UUIDs
with leading/trailing spaces or newlines are normalized; ensure you reference
the setter method setVolumeUuid and the field volumeUuid in class
APIEnableVolumeCacheMsg and preserve null-safety when trimming.

In
`@header/src/main/java/org/zstack/header/volume/localcache/DisableVolumeCacheMsg.java`:
- Around line 12-13: 在 DisableVolumeCacheMsg 的 setVolumeUuid 方法中对传入的 volumeUuid
做规范化:在赋值前调用 trim()(并处理 null 安全),例如在 setVolumeUuid(String volumeUuid) 中将
this.volumeUuid 设为 volumeUuid == null ? null : volumeUuid.trim(),确保后续使用该字段(如
UUID 比对/查询)不会因为首尾空白或换行导致匹配失败。

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java`:
- Around line 190-191: In KVMApiInterceptor (class KVMApiInterceptor) remove the
pointless "%s", "" placeholder from the error message construction so the
message reads without the trailing format placeholder; locate the string that
currently reads "cachePoolUuid or cacheMode is specified but neither
enableRootVolumeCache nor enableDataVolumeCache is set to true%s", "" and
replace it with the plain message "cachePoolUuid or cacheMode is specified but
neither enableRootVolumeCache nor enableDataVolumeCache is set to true" so no
formatting arguments are passed.
- Around line 106-110: The validate method
(validate(APICreateVmLocalVolumeCachePoolMsg msg)) currently uses msg.getName(),
msg.getHostUuid(), and msg.getUuid() directly when checking uniqueness and
ownership; update the code to trim incoming message fields first (e.g., apply
StringUtils.trimToNull or similar to msg.getName(), msg.getHostUuid(),
msg.getUuid() at the start of validate and any other interceptor methods) and
then use the trimmed values for DB queries and equality checks (the Q.New(...)
name/hostUuid comparisons and any subsequent ownership checks), ensuring
null/empty is handled consistently after trimming to prevent bypass by
leading/trailing whitespace.

In `@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CacheTO.java`:
- Around line 44-50: The valueOf method in CacheTO calls
inv.getCacheMode().name() which can NPE if cacheMode is null; update
CacheTO.valueOf to null-check inv.getCacheMode() (or use Optional) and only call
name() when non-null, otherwise setCacheMode to null (or a safe default/empty
string) so legacy/absent cacheMode values do not throw; reference the
CacheTO.valueOf method and VmLocalVolumeCacheInventory.getCacheMode() when
making the change and preserve existing behavior for non-null values.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheManagerImpl.java`:
- Around line 261-264: The code in VmLocalVolumeCacheManagerImpl parses
spec.getCacheMode() with VmLocalVolumeCacheMode.valueOf(...) without validating
or trimming input and does not handle IllegalArgumentException; update the
parsing in the method that contains specPoolUuid and cacheMode so you first
sanitize the string (null-check and trim), attempt to map it to
VmLocalVolumeCacheMode inside a try/catch, and on IllegalArgumentException call
completion.fail(...) with a clear error message (fall back to
VmLocalVolumeCacheMode.WriteBack only if intended), ensuring the same defensive
handling is applied where cacheMode is parsed later (the other valueOf usage
around lines 281-283).
- Around line 151-160: The cache injection queries in
VmLocalVolumeCacheManagerImpl use only volumeUuid and Instantiated state and
must also ensure the cache belongs to the target host; update the Q.New(...)
lookup (the places that create VmLocalVolumeCacheVO via
Q.New(...).eq(VmLocalVolumeCacheVO_.volumeUuid, volumeUuid).find() right before
volumeTO.setCache(...)) to also filter by the current host (e.g.
.eq(VmLocalVolumeCacheVO_.hostUuid, hostUuid)) or, if the VO stores poolUuid
only, resolve the pool's host and verify it matches hostUuid before using the
cache; apply this change consistently to all occurrences that set
CacheTO.valueOf(VmLocalVolumeCacheInventory.valueOf(cacheVO)) so only same-host
(or pool->host equal) instantiated caches are injected.
- Around line 299-317: The current early-return on existing cache
(VmLocalVolumeCacheVO existing with existing.getState() !=
VmLocalVolumeCacheState.None) is too broad and allows an Instantiated cache from
a different host to remain unchanged; change the logic to check host affinity:
if existing != null then compare existing.getHostUuid() with the current
hostUuid and only skip/return when the existing record is on the same host and
in a reusable state; otherwise treat the record as non-reusable by setting
existing.setState(VmLocalVolumeCacheState.Uninstantiated), update
existing.setCacheMode(cacheMode) and setPoolUuid (determine poolUuid via
selectBestPoolOnHost(hostUuid, volumeSize) when needed), then persist with
dbf.updateAndRefresh(existing) so cross-host stale records get reset and
reassigned.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolBase.java`:
- Around line 405-439: The two handlers
handle(APISyncVmLocalVolumeCachePoolCapacityMsg) and
handle(SyncCachePoolCapacityMsg) call syncPoolCapacity(...) directly and must be
serialized on the same queue as other pool ops; wrap the syncPoolCapacity
invocation inside a ChainTask submitted to the pool's syncThreadName (use the
existing ChainTask pattern used elsewhere in this class) so capacity sync runs
on the same serial thread as delete/reconnect/extend; inside the ChainTask's
run/next callback call syncPoolCapacity with the same ReturnValueCompletion
implementation and publish/reply the event/response from the completion
callbacks, preserving error handling.
- Around line 155-166: When deleting a pool in
deletePool(APIDeleteVmLocalVolumeCachePoolMsg msg, Completion completion) you
must first handle VmLocalVolumeCacheVO records that reference this poolUuid
instead of directly calling dbf.remove(self); query for VmLocalVolumeCacheVO
where poolUuid == self.getUuid(), then either block the delete by returning an
error if any active caches exist or atomically update those rows to clear/mark
their pool reference (e.g. set poolUuid=null or set status=ORPHAN) inside the
same DB transaction before calling dbf.remove(self); ensure you use the existing
DB transaction APIs and update the VM cache records and pool removal atomically
so no dangling VmLocalVolumeCacheVO.poolUuid references remain.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolCanonicalEvents.java`:
- Around line 50-55: The getters/setters for Date in
VmLocalVolumeCachePoolCanonicalEvents leak the mutable Date reference; change
getDate() to return a defensive copy (e.g., new Date(date.getTime()) or
null-safe equivalent) and change setDate(Date date) to store a copy of the
incoming Date (or null if input is null) so the internal field cannot be mutated
by external callers; update both methods (getDate and setDate) to handle null
safely.

In `@storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java`:
- Around line 1569-1577: In instantiateAndAttachCache, when the
AttachVolumeCacheMsg reply fails, add rollback cleanup for the cache created by
InstantiateLocalVolumeCacheMsg: detect the failure branch (where
reply.isSuccess() is false), extract the created cache identity from
ir.getInventory() (or the InstantiateLocalVolumeCacheMsg result), and issue a
cleanup operation (e.g., send a DeleteLocalVolumeCacheMsg or call the existing
delete/remove-cache routine with the cache UUID/path) before replying to
originMsg; ensure errors from the cleanup are logged but do not suppress
replying with originReply.setError(reply.getError()).

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstantiateCacheFlow.java`:
- Around line 54-63: The loop in VmInstantiateCacheFlow issues one DB query per
volumeUuid causing N+1 queries; replace it with a single batch query against
VmLocalVolumeCacheVO using an IN filter on volumeUuid plus conditions for state
== VmLocalVolumeCacheState.Uninstantiated and poolUuid != null, then iterate the
returned list to populate toInstantiate (matching by volumeUuid or simply adding
all results); update references to volumeUuids, VmLocalVolumeCacheVO,
VmLocalVolumeCacheState and toInstantiate accordingly to remove per-UUID Q.New
calls.

In `@compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java`:
- Around line 415-417: EnableVolumeCacheMsg 目前只设置了 volumeUuid(在
EnableVolumeCacheMsg 和 bus.makeLocalServiceId(..., VolumeConstant.SERVICE_ID)
处),会丢失上游传来的缓存池/模式配置;请在构造 EnableVolumeCacheMsg 时把上游的 cache pool 与 cache mode
字段透传到该消息(例如从调用上下文或 volumeInventory/请求对象中读取对应的缓存配置字段并通过 EnableVolumeCacheMsg 的对应
setter 赋值),确保发送前包含 cache pool 与 cache mode,保持原有 bus.makeLocalServiceId 调用不变。

In `@header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java`:
- Around line 412-421: The setters setCachePoolUuid and setCacheMode in
CreateVmInstanceMsg currently assign raw input strings; update both to trim
incoming values (e.g., String v = input == null ? null : input.trim()) and
normalize empty strings to null before assigning to this.cachePoolUuid and
this.cacheMode so leading/trailing whitespace or newlines from messages won’t
break UUID/mode matching.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIExtendVmLocalVolumeCachePoolEvent.java`:
- Around line 25-27: The __example__() in class
APIExtendVmLocalVolumeCachePoolEvent currently returns an empty event; update
APIExtendVmLocalVolumeCachePoolEvent.__example__ to construct and return an
event with a populated inventory (mirroring
APICreateVmLocalVolumeCachePoolEvent.__example__), e.g., create a
VmLocalVolumeCachePoolInventory instance, fill its fields with representative
sample values (uuid, name, description, type, url, zoneUuid, primaryStorageUuid,
createDate, lastOpDate, etc.), call event.setInventory(populatedInventory), and
then return the event so the API docs include a concrete inventory example.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CachePoolHostAllocatorFilter.java`:
- Around line 69-71: The code in CachePoolHostAllocatorFilter returns an empty
list via candidates.stream().filter(h -> false).collect(...), which is
unnecessarily complex; replace that expression with Collections.emptyList() and
add the import java.util.Collections; update the return in the relevant method
inside class CachePoolHostAllocatorFilter to return Collections.emptyList()
instead of using the stream/filter pattern.
- Around line 93-94: The code creates qualifiedHostUuids by streaming
hostsWithPool which is already a List<String>; replace the stream-based
conversion with a direct Set construction to simplify and improve readability:
in CachePoolHostAllocatorFilter replace the
hostsWithPool.stream().collect(Collectors.toSet()) expression used to assign
qualifiedHostUuids with either new HashSet<>(hostsWithPool) or
Set.copyOf(hostsWithPool) (Java 10+) so qualifiedHostUuids is built directly
from hostsWithPool.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolMessage.java`:
- Around line 3-5: 在 VmLocalVolumeCachePoolMessage 接口上添加 Javadoc
注释以说明接口用途和方法含义;具体在接口声明处为 VmLocalVolumeCachePoolMessage 添加一段简短描述(功能、使用场景),并为其方法
getPoolUuid() 添加 `@return` 描述说明返回值含义,必要时参考相似接口 VmLocalVolumeCacheMessage
的注释风格以保持一致性,并确保注释遵循项目的 Javadoc 规范。

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolVO.java`:
- Around line 43-47: The two capacity fields totalCapacity and availableCapacity
in VmLocalVolumeCachePoolVO are typed as double and should be changed to long to
avoid floating-point precision issues for byte-sized storage; update the field
declarations in class VmLocalVolumeCachePoolVO, change their types to long, and
adjust the corresponding getter/setter methods
(getTotalCapacity/setTotalCapacity and
getAvailableCapacity/setAvailableCapacity) to use long; also check any places
that read/write these fields (ORM mapping annotations on the fields and any code
that expects double) and update conversions or database column type mappings if
necessary to keep persistence consistent.

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java`:
- Around line 68-90: The copy constructor VolumeTO(VolumeTO other) currently
performs a shallow copy of the cache field (this.cache = other.cache), which can
lead to shared-reference issues; change it to perform a deep copy of CacheTO by
constructing a new CacheTO instance and copying its String fields (e.g., via new
CacheTO(other.getCache()) or by setting each field from other.getCache()) so
VolumeTO and other do not share the same CacheTO reference; update the
constructor to use the new CacheTO copy and ensure getters/setters used are
CacheTO-specific (getCache(), setCache(...)).

In `@storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java`:
- Around line 694-708: In validate(APIEnableVolumeCacheMsg msg) trim the
incoming volumeUuid (e.g., String volumeUuid = msg.getVolumeUuid().trim())
before using it in dbf.isExist(...) and the Q.New(...).eq(...) lookup and in
exception messages; ensure you handle null safely (check msg.getVolumeUuid() for
null) so the validation uses the trimmed value for existence checks and
comparisons (refer to validate, volumeUuid, dbf.isExist,
Q.New(VmLocalVolumeCacheVO.class) and VmLocalVolumeCacheVO_.volumeUuid).
- Around line 710-723: The validate(APIDisableVolumeCacheMsg msg) method uses
volumeUuid directly; trim the input like in validate(APIEnableVolumeCacheMsg) by
obtaining a trimmed variable (e.g., String volumeUuid = msg.getVolumeUuid() ==
null ? null : msg.getVolumeUuid().trim()), validate it for null/empty before
calling dbf.isExist(…, VolumeVO.class), and then use that trimmed variable in
the Q.New(VmLocalVolumeCacheVO.class).eq(VmLocalVolumeCacheVO_.volumeUuid,
volumeUuid).find() and subsequent checks against VmLocalVolumeCacheState.None to
ensure whitespace from APIDisableVolumeCacheMsg is handled.

In `@storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java`:
- Around line 1516-1518: Replace the two Timestamp constructions using new
java.util.Date().getTime() for cacheVO timestamps with the database-synchronized
time from dbf.getCurrentSqlTime(); in VolumeManagerImpl where
cacheVO.setCreateDate(...) and cacheVO.setLastOpDate(...) are called (just
before dbf.persist(cacheVO)), call new
Timestamp(dbf.getCurrentSqlTime().getTime()) or otherwise use
dbf.getCurrentSqlTime() directly to set both create and last-op dates so the
persisted timestamps are consistent with DB time.

ℹ️ Review info

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf48ab8 and b7f8bd3.

⛔ Files ignored due to path filters (6)
  • conf/globalConfig/kvm.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/serviceConfig/volume.xml is excluded by !**/*.xml
  • conf/springConfigXml/Kvm.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
📒 Files selected for processing (88)
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateCacheFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • header/src/main/java/org/zstack/header/volume/localcache/APIDisableVolumeCacheEvent.java
  • header/src/main/java/org/zstack/header/volume/localcache/APIDisableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/APIEnableVolumeCacheEvent.java
  • header/src/main/java/org/zstack/header/volume/localcache/APIEnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/AttachVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/AttachVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/DeleteLocalVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/DeleteLocalVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/DisableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/DisableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/EnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/EnableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/InstantiateLocalVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/InstantiateLocalVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/LiveDetachVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/LiveDetachVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/OfflineDetachVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/volume/localcache/OfflineDetachVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheConstant.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheMessage.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheMode.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheVO.java
  • header/src/main/java/org/zstack/header/volume/localcache/VmLocalVolumeCacheVO_.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIChangeVmLocalVolumeCachePoolStateEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIChangeVmLocalVolumeCachePoolStateMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APICreateVmLocalVolumeCachePoolEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APICreateVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIDeleteVmLocalVolumeCachePoolEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIDeleteVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIExtendVmLocalVolumeCachePoolEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIExtendVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIQueryVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIQueryVmLocalVolumeCachePoolReply.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIReconnectVmLocalVolumeCachePoolEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIReconnectVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APISyncVmLocalVolumeCachePoolCapacityEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APISyncVmLocalVolumeCachePoolCapacityMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIUpdateVmLocalVolumeCachePoolEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/APIUpdateVmLocalVolumeCachePoolMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CachePoolHostAllocatorFilter.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/CacheTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/RBACInfo.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/SyncCachePoolCapacityMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/SyncCachePoolCapacityReply.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheBase.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheManagerImpl.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolBase.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolCanonicalEvents.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolGlobalConfig.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolInventory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolManagerImpl.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolMessage.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolState.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolStatus.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolVO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolVO_.java
  • storage/src/main/java/org/zstack/storage/volume/DisableVolumeCacheLongJob.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java

Comment on lines +154 to +161
if (enableRoot) {
// Root volume size: from image size or root disk offering
ImageInventory image = spec.getImageSpec().getInventory();
if (image != null && image.getSize() != 0) {
totalCacheSize += image.getSize();
} else if (spec.getRootDiskOffering() != null) {
totalCacheSize += spec.getRootDiskOffering().getDiskSize();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

根卷缓存大小计算缺少 ISO 分支,可能导致主机分配误判。

当前只要 image 存在就优先用 image.getSize()。但 ISO 场景根卷大小应来自 root disk offering(与本文件 prepareMsg 的根盘计算逻辑不一致),否则会把 ISO 体积当作缓存需求,放大 requiredSize 约束。

🔧 建议修改
 if (enableRoot) {
-    // Root volume size: from image size or root disk offering
-    ImageInventory image = spec.getImageSpec().getInventory();
-    if (image != null && image.getSize() != 0) {
-        totalCacheSize += image.getSize();
-    } else if (spec.getRootDiskOffering() != null) {
-        totalCacheSize += spec.getRootDiskOffering().getDiskSize();
-    }
+    // Root volume size: ISO场景取root disk offering,其它镜像优先取image size
+    ImageInventory image = spec.getImageSpec() == null ? null : spec.getImageSpec().getInventory();
+    boolean nonIsoImage = image != null && !ImageMediaType.ISO.toString().equals(image.getMediaType());
+    if (nonIsoImage && image.getSize() > 0) {
+        totalCacheSize += image.getSize();
+    } else if (spec.getRootDiskOffering() != null) {
+        totalCacheSize += spec.getRootDiskOffering().getDiskSize();
+    }
 }
🤖 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/VmAllocateHostFlow.java` around
lines 154 - 161, The root-cache-size calculation currently uses image.getSize()
whenever an ImageInventory exists, which treats ISOs as having cache size and
mismatches the prepareMsg logic; update the block in VmAllocateHostFlow where
enableRoot is handled so that you only use image.getSize() when the image is not
an ISO and size != 0, otherwise fall back to
spec.getRootDiskOffering().getDiskSize() (i.e., change the condition around
ImageInventory image = spec.getImageSpec().getInventory() to check image media
type/ISO status before using image.getSize()); keep totalCacheSize accumulation
and ensure this logic mirrors the root-size calculation in prepareMsg.

Comment on lines +7 to +8
`totalCapacity` DOUBLE NOT NULL DEFAULT 0,
`availableCapacity` DOUBLE NOT NULL DEFAULT 0,
Copy link

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
# Check how capacity-related fields are defined across the codebase
rg -n "Capacity.*DOUBLE|Capacity.*BIGINT|Capacity.*INT|capacity.*DOUBLE|capacity.*BIGINT|capacity.*INT" --type sql -C 2

Repository: MatheMatrix/zstack

Length of output: 3173


容量字段应使用 BIGINT 而非 DOUBLE 类型。

totalCapacityavailableCapacity 目前使用 DOUBLE 类型,但根据代码库中的其他版本(如 V5.5.0、V3.9.0、V3.4.2),容量字段统一使用 BIGINTBIGINT UNSIGNED。使用 DOUBLE 会引入浮点数精度问题,不适合存储容量值(通常为字节数)。建议改为 BIGINT UNSIGNED 以保持一致性。

另外,注意这两个字段添加了 NOT NULL DEFAULT 0,在升级场景中需要确认现有数据的处理方式。

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

In `@conf/db/upgrade/V5.5.12__schema.sql` around lines 7 - 8, The capacity columns
`totalCapacity` and `availableCapacity` are currently defined as DOUBLE; change
their types to BIGINT UNSIGNED to avoid floating-point precision issues and
match prior schema versions (use `totalCapacity` BIGINT UNSIGNED NOT NULL
DEFAULT 0 and `availableCapacity` BIGINT UNSIGNED NOT NULL DEFAULT 0). Update
the ALTER/CREATE statement in V5.5.12__schema.sql to use BIGINT UNSIGNED and
ensure any migration path handles existing rows (e.g., cast/validate current
values to integers and apply DEFAULT 0 for NULLs) so upgrades don’t lose or
corrupt capacity data.

Comment on lines +22 to +34
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

HostBlockDeviceStruct that = (HostBlockDeviceStruct) o;
return wwid != null ? wwid.equals(that.wwid) : that.wwid == null;
}

@Override
public int hashCode() {
return wwid != null ? wwid.hashCode() : 0;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

避免把 wwid 为空的不同设备判定为相等

Line 28-33 当前逻辑会把所有 wwid == null 的对象视为相等,这会在集合去重或比对时吞掉真实设备条目。建议在 wwid 为空时回退到稳定次级键(如 path + hctl)比较与哈希。

建议修改
+import java.util.Objects;
@@
     `@Override`
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;

         HostBlockDeviceStruct that = (HostBlockDeviceStruct) o;
-        return wwid != null ? wwid.equals(that.wwid) : that.wwid == null;
+        if (wwid != null && that.wwid != null) {
+            return wwid.equals(that.wwid);
+        }
+        return Objects.equals(path, that.path) && Objects.equals(hctl, that.hctl);
     }

     `@Override`
     public int hashCode() {
-        return wwid != null ? wwid.hashCode() : 0;
+        return wwid != null ? wwid.hashCode() : Objects.hash(path, hctl);
     }
🤖 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/host/HostBlockDeviceStruct.java`
around lines 22 - 34, The equals/hashCode pair in HostBlockDeviceStruct
currently treats all instances with wwid == null as equal; change the logic in
equals(Object o) and hashCode() so that if wwid is non-null you compare/hash by
wwid, but if wwid is null you fall back to a stable secondary key composed of
path and hctl (e.g., compare path.equals(...) and hctl.equals(...) and compute
hash from path and hctl). Update both HostBlockDeviceStruct.equals and
HostBlockDeviceStruct.hashCode to use the same wwid-first else path+hctl
strategy so null-wwid devices are distinguished consistently.

Comment on lines +5 to +6
public class APIDisableVolumeCacheEvent extends APIEvent {
private VmLocalVolumeCacheInventory inventory;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

缺少 @RestResponse 注解

APIEnableVolumeCacheEvent 相同,此类包含 inventory 字段但缺少 @RestResponse(allTo = "inventory") 注解。建议添加以保持与其他 API Event 类的一致性。

🔧 建议修复
 package org.zstack.header.volume.localcache;
 
 import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
 
+@RestResponse(allTo = "inventory")
 public class APIDisableVolumeCacheEvent extends APIEvent {
🤖 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/localcache/APIDisableVolumeCacheEvent.java`
around lines 5 - 6, APIDisableVolumeCacheEvent is missing the `@RestResponse`
annotation for its inventory field; update the class APIDisableVolumeCacheEvent
to add the `@RestResponse`(allTo = "inventory") annotation (same as
APIEnableVolumeCacheEvent) so the VmLocalVolumeCacheInventory inventory is
exposed correctly in REST responses.

Comment on lines +5 to +6
public class APIEnableVolumeCacheEvent extends APIEvent {
private VmLocalVolumeCacheInventory inventory;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

缺少 @RestResponse 注解

参考其他类似的 API Event 类(如 APIExtendVmLocalVolumeCachePoolEventAPIUpdateVmLocalVolumeCachePoolEvent 等),当 Event 类包含 inventory 字段时,应添加 @RestResponse(allTo = "inventory") 注解以确保 REST 响应正确序列化。

🔧 建议修复
 package org.zstack.header.volume.localcache;
 
 import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
 
+@RestResponse(allTo = "inventory")
 public class APIEnableVolumeCacheEvent extends APIEvent {
🤖 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/localcache/APIEnableVolumeCacheEvent.java`
around lines 5 - 6, The APIEnableVolumeCacheEvent class declares a
VmLocalVolumeCacheInventory field named inventory but is missing the REST
response annotation; add the annotation `@RestResponse`(allTo = "inventory") to
the APIEnableVolumeCacheEvent class (same pattern used in
APIExtendVmLocalVolumeCachePoolEvent and APIUpdateVmLocalVolumeCachePoolEvent)
so the inventory is correctly serialized in REST responses.

Comment on lines +299 to +317
VmLocalVolumeCacheVO existing = Q.New(VmLocalVolumeCacheVO.class)
.eq(VmLocalVolumeCacheVO_.volumeUuid, volumeUuid)
.find();
if (existing != null && existing.getState() != VmLocalVolumeCacheState.None) {
logger.debug(String.format("volume[uuid:%s] already has cache in state[%s], skip creating",
volumeUuid, existing.getState()));
return;
}

String poolUuid = specPoolUuid;
if (poolUuid == null) {
poolUuid = selectBestPoolOnHost(hostUuid, volumeSize);
}

if (existing != null) {
existing.setState(VmLocalVolumeCacheState.Uninstantiated);
existing.setCacheMode(cacheMode);
existing.setPoolUuid(poolUuid);
dbf.updateAndRefresh(existing);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

已有缓存记录的复用条件过宽,跨主机场景不会重置记录

Line 302 只要 existing.getState() != None 就直接跳过。若该记录是旧主机上的 Instantiated 缓存,本次不会更新 poolUuid/state,会长期保留过期上下文。建议仅在“当前主机可复用”时跳过,否则重置为 Uninstantiated 并重新分配池。

建议修复(增加主机一致性判定)
-        if (existing != null && existing.getState() != VmLocalVolumeCacheState.None) {
-            logger.debug(String.format("volume[uuid:%s] already has cache in state[%s], skip creating",
-                    volumeUuid, existing.getState()));
-            return;
-        }
+        if (existing != null && existing.getState() != VmLocalVolumeCacheState.None) {
+            String existingPoolHostUuid = existing.getPoolUuid() == null ? null
+                    : Q.New(VmLocalVolumeCachePoolVO.class)
+                    .select(VmLocalVolumeCachePoolVO_.hostUuid)
+                    .eq(VmLocalVolumeCachePoolVO_.uuid, existing.getPoolUuid())
+                    .findValue();
+            if (hostUuid.equals(existingPoolHostUuid)) {
+                logger.debug(String.format("volume[uuid:%s] already has reusable cache in state[%s], skip creating",
+                        volumeUuid, existing.getState()));
+                return;
+            }
+            existing.setState(VmLocalVolumeCacheState.Uninstantiated);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCacheManagerImpl.java`
around lines 299 - 317, The current early-return on existing cache
(VmLocalVolumeCacheVO existing with existing.getState() !=
VmLocalVolumeCacheState.None) is too broad and allows an Instantiated cache from
a different host to remain unchanged; change the logic to check host affinity:
if existing != null then compare existing.getHostUuid() with the current
hostUuid and only skip/return when the existing record is on the same host and
in a reusable state; otherwise treat the record as non-reusable by setting
existing.setState(VmLocalVolumeCacheState.Uninstantiated), update
existing.setCacheMode(cacheMode) and setPoolUuid (determine poolUuid via
selectBestPoolOnHost(hostUuid, volumeSize) when needed), then persist with
dbf.updateAndRefresh(existing) so cross-host stale records get reset and
reassigned.

Comment on lines +155 to +166
private void deletePool(APIDeleteVmLocalVolumeCachePoolMsg msg, Completion completion) {
KVMAgentCommands.DeletePoolCmd cmd = new KVMAgentCommands.DeletePoolCmd();
cmd.poolUuid = self.getUuid();
cmd.mountPoint = self.getMetadata();

httpCall(KVMConstant.LOCAL_VOLUME_CACHE_DELETE_POOL_PATH, self.getHostUuid(), cmd,
KVMAgentCommands.PoolRsp.class, new ReturnValueCompletion<KVMAgentCommands.PoolRsp>(completion) {
@Override
public void success(KVMAgentCommands.PoolRsp rsp) {
dbf.remove(self);
completion.success();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

删除池时未处理关联缓存记录,可能产生悬挂引用

Line 164 直接 dbf.remove(self),但仍引用该池的 VmLocalVolumeCacheVO.poolUuid 记录未被阻断或清理。后续缓存侧按 poolUuid 查池时会触发空指针路径,并且这些缓存记录也会脱离正常容量/GC管理。

建议在删除前先校验并阻断(或原子清理关联缓存状态)再删除池。

💡 建议修改(阻断删除示例)
 private void deletePool(APIDeleteVmLocalVolumeCachePoolMsg msg, Completion completion) {
+    long activeCacheCount = org.zstack.core.db.Q.New(org.zstack.header.volume.localcache.VmLocalVolumeCacheVO.class)
+            .eq(org.zstack.header.volume.localcache.VmLocalVolumeCacheVO_.poolUuid, self.getUuid())
+            .eq(org.zstack.header.volume.localcache.VmLocalVolumeCacheVO_.state,
+                    org.zstack.header.volume.localcache.VmLocalVolumeCacheState.Instantiated)
+            .count();
+    if (activeCacheCount > 0) {
+        completion.fail(operr("pool[uuid:%s] still has %s instantiated caches, detach/delete caches first",
+                self.getUuid(), activeCacheCount));
+        return;
+    }
+
     KVMAgentCommands.DeletePoolCmd cmd = new KVMAgentCommands.DeletePoolCmd();
     cmd.poolUuid = self.getUuid();
     cmd.mountPoint = self.getMetadata();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolBase.java`
around lines 155 - 166, When deleting a pool in
deletePool(APIDeleteVmLocalVolumeCachePoolMsg msg, Completion completion) you
must first handle VmLocalVolumeCacheVO records that reference this poolUuid
instead of directly calling dbf.remove(self); query for VmLocalVolumeCacheVO
where poolUuid == self.getUuid(), then either block the delete by returning an
error if any active caches exist or atomically update those rows to clear/mark
their pool reference (e.g. set poolUuid=null or set status=ORPHAN) inside the
same DB transaction before calling dbf.remove(self); ensure you use the existing
DB transaction APIs and update the VM cache records and pool removal atomically
so no dangling VmLocalVolumeCacheVO.poolUuid references remain.

Comment on lines +405 to +439
private void handle(APISyncVmLocalVolumeCachePoolCapacityMsg msg) {
APISyncVmLocalVolumeCachePoolCapacityEvent event = new APISyncVmLocalVolumeCachePoolCapacityEvent(msg.getId());

syncPoolCapacity(new ReturnValueCompletion<VmLocalVolumeCachePoolInventory>(msg) {
@Override
public void success(VmLocalVolumeCachePoolInventory inv) {
event.setInventory(inv);
bus.publish(event);
}

@Override
public void fail(ErrorCode errorCode) {
event.setError(errorCode);
bus.publish(event);
}
});
}

private void handle(SyncCachePoolCapacityMsg msg) {
syncPoolCapacity(new ReturnValueCompletion<VmLocalVolumeCachePoolInventory>(msg) {
@Override
public void success(VmLocalVolumeCachePoolInventory inv) {
SyncCachePoolCapacityReply reply = new SyncCachePoolCapacityReply();
reply.setInventory(inv);
bus.reply(msg, reply);
}

@Override
public void fail(ErrorCode errorCode) {
SyncCachePoolCapacityReply reply = new SyncCachePoolCapacityReply();
reply.setError(errorCode);
bus.reply(msg, reply);
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

容量同步入口未串行化,存在并发覆盖风险

Line 405 和 Line 423 的两个 sync handler 直接调用 syncPoolCapacity(...),没有进入 syncThreadName 对应的 ChainTask。这会与 delete/reconnect/extend 并发,带来状态回写覆盖或对已删除对象执行更新的风险。

建议将这两个入口也纳入同一串行队列,和其它 pool 操作保持一致的同步语义。

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

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolBase.java`
around lines 405 - 439, The two handlers
handle(APISyncVmLocalVolumeCachePoolCapacityMsg) and
handle(SyncCachePoolCapacityMsg) call syncPoolCapacity(...) directly and must be
serialized on the same queue as other pool ops; wrap the syncPoolCapacity
invocation inside a ChainTask submitted to the pool's syncThreadName (use the
existing ChainTask pattern used elsewhere in this class) so capacity sync runs
on the same serial thread as delete/reconnect/extend; inside the ChainTask's
run/next callback call syncPoolCapacity with the same ReturnValueCompletion
implementation and publish/reply the event/response from the completion
callbacks, preserving error handling.

Comment on lines +50 to +55
public Date getDate() {
return date;
}

public void setDate(Date date) {
this.date = date;
Copy link

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:

# 搜索是否有对 getDate() 返回值进行修改的操作
rg -nP --type=java '\.getDate\s*\(\)\s*\.\s*set(?:Time|Year|Month|Date|Hours|Minutes|Seconds|Full)\s*\(' plugin/kvm

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 搜索是否有调用 setDate(null) 的代码
rg -nP --type=java 'setDate\s*\(\s*null\s*\)' plugin/kvm

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查看 PoolDisconnectedData 类的完整内容和使用情况
cat -n plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolCanonicalEvents.java | head -100

Repository: MatheMatrix/zstack

Length of output: 1829


🏁 Script executed:

# 搜索在其他文件中 PoolDisconnectedData 的使用情况
rg -n 'PoolDisconnectedData' plugin/kvm --type=java -A3 -B1

Repository: MatheMatrix/zstack

Length of output: 1722


Date 可变引用泄漏,应添加防御性复制。

getDate() 直接返回内部 Date 引用,setDate() 直接持有外部引用,调用方可在类外修改该日期对象。虽然当前代码中未发现此类修改,但为防止数据被意外改变,建议采用防御性复制方案。

建议修改
         public Date getDate() {
-            return date;
+            return date == null ? null : new Date(date.getTime());
         }

         public void setDate(Date date) {
-            this.date = date;
+            this.date = date == null ? null : new Date(date.getTime());
         }
📝 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 Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
public Date getDate() {
return date == null ? null : new Date(date.getTime());
}
public void setDate(Date date) {
this.date = date == null ? null : new Date(date.getTime());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/kvm/src/main/java/org/zstack/kvm/localvolumecache/VmLocalVolumeCachePoolCanonicalEvents.java`
around lines 50 - 55, The getters/setters for Date in
VmLocalVolumeCachePoolCanonicalEvents leak the mutable Date reference; change
getDate() to return a defensive copy (e.g., new Date(date.getTime()) or
null-safe equivalent) and change setDate(Date date) to store a copy of the
incoming Date (or null if input is null) so the internal field cannot be mutated
by external callers; update both methods (getDate and setDate) to handle null
safely.

Comment on lines +1569 to +1577
if (!reply.isSuccess()) {
originReply.setError(reply.getError());
} else {
originReply.setInventory(ir.getInventory());
}
bus.reply(originMsg, originReply);
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

热挂载失败时缺少回滚清理逻辑

instantiateAndAttachCache 方法中,如果 AttachVolumeCacheMsg 失败(Line 1569),之前已经通过 InstantiateLocalVolumeCacheMsg 创建的缓存文件不会被清理。这可能导致磁盘上残留孤立的缓存文件。

🐛 建议添加回滚逻辑
                 bus.send(amsg, new CloudBusCallBack(originMsg) {
                     `@Override`
                     public void run(MessageReply reply) {
                         if (!reply.isSuccess()) {
+                            // Rollback: delete the instantiated cache file
+                            DeleteLocalVolumeCacheMsg delMsg = new DeleteLocalVolumeCacheMsg();
+                            delMsg.setCacheUuid(cacheUuid);
+                            bus.makeTargetServiceIdByResourceUuid(delMsg, VmLocalVolumeCacheConstant.CACHE_SERVICE_ID, cacheUuid);
+                            bus.send(delMsg, new CloudBusCallBack(originMsg) {
+                                `@Override`
+                                public void run(MessageReply delReply) {
+                                    if (!delReply.isSuccess()) {
+                                        logger.warn(String.format("failed to rollback cache[uuid:%s]: %s",
+                                                cacheUuid, delReply.getError()));
+                                    }
+                                    originReply.setError(reply.getError());
+                                    bus.reply(originMsg, originReply);
+                                }
+                            });
+                            return;
-                            originReply.setError(reply.getError());
                         } else {
                             originReply.setInventory(ir.getInventory());
                         }
                         bus.reply(originMsg, originReply);
                     }
                 });
🤖 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/volume/VolumeManagerImpl.java`
around lines 1569 - 1577, In instantiateAndAttachCache, when the
AttachVolumeCacheMsg reply fails, add rollback cleanup for the cache created by
InstantiateLocalVolumeCacheMsg: detect the failure branch (where
reply.isSuccess() is false), extract the created cache identity from
ir.getInventory() (or the InstantiateLocalVolumeCacheMsg result), and issue a
cleanup operation (e.g., send a DeleteLocalVolumeCacheMsg or call the existing
delete/remove-cache routine with the cache UUID/path) before replying to
originMsg; ensure errors from the cleanup are logged but do not suppress
replying with originReply.setError(reply.getError()).

@MatheMatrix
Copy link
Owner

Comment from yaohua.wu:

Review: MR !9255 — ZSTAC-81715 (Java 侧:VM 本地卷缓存)

本 MR 实现 VM 本地卷缓存功能(CachePool 管理 + 卷缓存生命周期 + VM 启动集成),共 94 个文件变更。以下结合 Jira ZSTAC-81715 需求逐文件审查。

Critical

  1. [VmLocalVolumeCachePoolVO.java] 容量字段使用 double 类型availableCapacity / totalCapacity / totalPhysicalCapacity / availablePhysicalCapacity 声明为 double,存储容量应使用 long(单位 bytes)。double 的精度只有 ~15 位有效数字,超过 8PB 时会丢失精度,且浮点比较不可靠(available < required 可能因精度误判)。数据库 schema 应使用 BIGINT,Java 侧使用 long

  2. [VmLocalVolumeCachePoolManagerImpl.java] ReturnValueCompletion(null) 吞掉错误 — 多处使用 new ReturnValueCompletion<T>(null) 作为回调,当 fail() 被调用时,父 completion 为 null,错误被静默丢弃。例如 GC 任务中的容量回收失败、reconnect 中的 sync 失败等,都不会有任何日志或告警。建议至少传入一个 NoErrorCompletion 或显式 log error。

  3. [VmLocalVolumeCacheManagerImpl.java] injectCacheForVolume 忽略 hostUuid 参数 — 方法签名接受 hostUuid 参数,但内部查询 VmLocalVolumeCacheVO 时没有按 hostUuid 过滤,而是取了所有 host 上的缓存记录。当 VM 迁移到新 host 后,可能注入旧 host 上已失效的缓存路径,导致 IO 错误或数据损坏。建议在查询中添加 .eq(VmLocalVolumeCacheVO_.hostUuid, hostUuid)

  4. [VolumeManagerImpl.java] DisableVolumeCacheMsg 的 VM 状态检查存在 TOCTOU 竞态handleDisableVolumeCache() 先检查所有挂载该卷的 VM 是否处于 Stopped 状态,然后执行禁用操作。在检查和执行之间,VM 可能被启动,导致正在运行的 VM 的缓存被拔除。建议在实际禁用时再次校验 VM 状态,或使用 Q.New(...).eq(status, Running).isExists() 作为原子检查。

  5. [V5.5.12__schema.sql] 缺少 installPathVmLocalVolumeCachePoolVO 的 Java 定义中声明了 installPath 字段(@Column),但 flyway schema 中 CREATE TABLE VmLocalVolumeCachePoolVO 没有该列。Hibernate 启动时会因 schema 不匹配报错,或者如果用了 hbm2ddl.auto=update 会自动加列但生产环境通常不开启。需要在 schema 中补上 installPath varchar(1024) DEFAULT NULL

  6. [VmLocalVolumeCachePoolManagerImpl.java] GC 任务中 N+1 查询gcDeletedPool() 遍历所有已删除的 pool VO,对每个 pool 再查询其关联的 cache 记录并逐条处理。当有大量已删除 pool 时(如批量清理场景),会产生 N+1 查询风暴。建议批量查询 + 批量删除。

Warning

  1. [VmLocalVolumeCachePoolBase.java] self 字段可变但不重新从 DB 加载self 在构造时赋值后,后续操作可能修改了 DB 中的状态(如其他管理节点),但 self 引用不会刷新。长时间持有的 self 可能导致脏读。建议在关键操作前 self = dbf.reload(self) 或使用 Q.New() 重新查询。

  2. [VmLocalVolumeCachePoolVO.java] 容量计算可能溢出 — 即使修正为 long,多个大容量 pool 的容量求和可能溢出 long。建议使用 Math.addExact() 或在汇总时检查溢出。

  3. [VmLocalVolumeCachePoolBase.java] 状态变更使用字符串比较changeState() 中使用 current.toString().equals(next.toString()) 判断状态是否变化,如果 enum 的 toString() 被覆盖,可能产生误判。建议直接用 current == nextcurrent.equals(next)

  4. [VmLocalVolumeCachePoolManagerImpl.java] 周期性任务无 leader electionsyncCachePool() 和 GC 任务在所有管理节点上运行,没有使用 @AsyncThread + leader election 机制。多节点环境下会重复执行相同清理/同步操作,浪费资源且可能冲突。建议使用 gcfGlobalConfig 的 leader-only 机制。

  5. [VmInstantiateCacheFlow.java] 缓存注入串行执行 — 多个卷的缓存创建是串行逐个执行的(FlowChain 中每个 volume 一个 flow)。如果 VM 挂载了多个缓存卷,启动延迟会线性增长。建议并行创建缓存(使用 AsyncLatchWhileCompletion)。

  6. [KVMAgentCommands.java] 命令 DTO 字段全部 public — 新增的 cache 相关命令类(CreateVolumeCacheCmdDeleteVolumeCacheCmd 等)的字段全部为 public,没有 getter/setter。虽然这可能是项目现有 pattern,但 public 字段缺少验证入口,无法在 setter 中加校验逻辑。

  7. [VmLocalVolumeCacheManagerImpl.java] createCacheRecord 静默忽略 null poolUuid — 当找不到匹配的 cache pool 时,poolUuid 为 null,方法直接将 null 写入 VmLocalVolumeCacheVO.poolUuid。后续按 poolUuid 查询时会漏掉这些记录,导致容量统计不准确和 GC 遗漏。建议在 poolUuid 为 null 时抛出明确错误或 fallback 处理。

  8. [VolumeManagerImpl.java] Enable/Disable 缓存无并发保护handleEnableVolumeCache()handleDisableVolumeCache() 没有对同一个 volume 的并发调用做互斥。如果用户快速连续点击启用/禁用,可能导致状态不一致。建议使用 thdf.chainSubmit() 或对 volume UUID 加锁。

Suggestion

  1. [VmLocalVolumeCachePoolManagerImpl.java] 魔法数字 — 代码中直接使用数字常量(如 GC 间隔、重试次数),建议提取为 GlobalConfig 或类常量,便于运维调优。

  2. [CachePoolHostAllocatorFilter.java] 过滤逻辑可合并 — Host allocator filter 中多次查询 DB 判断 host 是否有可用 cache pool,可以合并为一次批量查询提高效率。

  3. [VmLocalVolumeCachePoolBase.java] 日志信息不足 — 状态变更、容量变更等关键操作的日志缺少上下文信息(如变更前后的值、触发原因),不利于问题排查。

  4. [schema] 建议为高频查询字段添加索引VmLocalVolumeCacheVOvolumeUuid + hostUuid 组合查询频繁,建议添加复合索引。VmLocalVolumeCachePoolVOhostUuid 也建议加索引。

  5. [VmLocalVolumeCacheManagerImpl.java] 缓存命中判断硬编码 — 判断是否使用缓存的逻辑分散在多处,建议提取为统一的 CachePolicy 策略类,便于后续扩展(如按卷类型、IO 模式决定是否缓存)。

  6. [全局] 缺少监控指标 — 缓存命中率、缓存池利用率、缓存创建/回收次数等关键指标没有暴露。建议集成现有的监控框架,暴露 Prometheus metrics 或通过 GlobalConfig 查询。

Verdict: REVISION_REQUIRED

Critical 项(尤其是 C-1 容量精度、C-3 缓存注入忽略 host、C-5 schema 缺列)必须修复后才能合入。


🤖 Robot Reviewer

@MatheMatrix MatheMatrix force-pushed the sync/haidong.pang/feature/ZSTAC-81715 branch from b7f8bd3 to 959fe16 Compare March 3, 2026 13:45
Copy link

@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

Caution

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

⚠️ Outside diff range comments (2)
plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java (1)

67-89: ⚠️ Potential issue | 🟡 Minor

拷贝构造函数中的 cache 字段应进行深拷贝。

Line 88 直接赋值 this.cache = other.cache 会使两个 VolumeTO 实例共享同一个 CacheTO 对象。由于 CacheTO 是可变的(提供了 setCacheUuidsetPoolUuid 等 setter 方法),任一实例对 cache 的修改都会影响另一实例,导致数据一致性问题。建议:

  • CacheTO 添加拷贝构造函数,在此处进行深拷贝:this.cache = other.cache == null ? null : new CacheTO(other.cache);
  • 或在 CacheTO 中实现 clone() 方法
  • 或在注释中明确说明该共享语义是有意设计
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java` around lines 67 - 89,
The VolumeTO copy constructor currently shallow-copies the cache field
(this.cache = other.cache), causing two VolumeTO instances to share a mutable
CacheTO; update the copy logic to perform a deep copy: add a copy constructor or
clone method to CacheTO (e.g., CacheTO(CacheTO other) or CacheTO.clone()) and
change VolumeTO(VolumeTO other) to set this.cache = other.cache == null ? null :
new CacheTO(other.cache) (or use other.cache.clone()), so each VolumeTO gets its
own CacheTO instance; alternatively, if sharing is intentional, add a clear
comment in VolumeTO and CacheTO documenting the shared, mutable semantics.
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java (1)

74-75: ⚠️ Potential issue | 🟡 Minor

Javadoc 中包含中文文本

Line 74 的 @summary 创建云主机 包含中文,违反编码规范要求"代码里不应当有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文"。

建议修改
-* `@summary` 创建云主机
+* `@summary` Create a VM instance

As per coding guidelines: "代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写"。

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

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java` around
lines 74 - 75, The Javadoc `@summary` contains Chinese text; update the class
Javadoc for APICreateVmInstanceMsg to use English (e.g., replace "@summary
创建云主机" with a clear English summary like "@summary Create VM instance" or
"@summary Create virtual machine") ensuring the comment contains no Chinese
characters and follows existing project wording style.
🧹 Nitpick comments (5)
plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java (1)

724-728: 建议对 blockDevices 做空值归一化,避免向上游透传 null。

当前成功分支直接 reply.setBlockDevices(rsp.getBlockDevices()),当 agent 返回 null 时会把判空成本转移给调用方。建议统一返回空集合。

可选修改示例
             public void success(KVMAgentCommands.GetBlockDevicesRsp rsp) {
                 if (!rsp.isSuccess()) {
                     reply.setError(operr("operation error, because:%s", rsp.getError()));
                 } else {
-                    reply.setBlockDevices(rsp.getBlockDevices());
+                    reply.setBlockDevices(rsp.getBlockDevices() == null
+                            ? Collections.emptyList()
+                            : rsp.getBlockDevices());
                 }
                 bus.reply(msg, reply);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java` around lines 724 - 728,
The success branch currently calls reply.setBlockDevices(rsp.getBlockDevices())
which can propagate a null to callers; change it to normalize null to an empty
collection (e.g. if rsp.getBlockDevices() == null then set an empty List) before
calling reply.setBlockDevices so callers always receive a non-null list; update
the success branch in the method where reply.setBlockDevices and
rsp.getBlockDevices are used (KVMHost related handling).
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java (1)

42-46: 建议:容量计算时考虑溢出保护

totalCapacityavailableCapacity 使用 long 类型是正确的。但在进行容量加减计算时,建议使用 Math.addExact()Math.subtractExact() 来检测溢出,避免在极端场景(>8PB 容量累加)下产生静默溢出。

🤖 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/localVolumeCache/VmLocalVolumeCachePoolVO.java`
around lines 42 - 46, The capacity fields totalCapacity and availableCapacity in
VmLocalVolumeCachePoolVO are declared as long but arithmetic on them can
silently overflow; update any methods that modify these fields (e.g., code paths
that add or subtract capacity deltas such as capacity allocation/release
helpers) to use Math.addExact(...) and Math.subtractExact(...) (or wrap those
calls to catch ArithmeticException) when computing new totals so overflows are
detected and handled; ensure exceptions are translated to a clear error/rollback
path rather than letting incorrect values be written.
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java (2)

241-243: 建议为 DiskAO.cacheMode 添加有效值约束

APICreateVmInstanceMsg 的顶层 cacheMode 字段有 validValues = {"WriteBack"} 约束,但 DiskAO 内部类的 cacheMode 字段没有相应的约束。为保持一致性和数据校验,建议添加相同的约束或通过文档说明有效值。

🤖 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/APICreateVmInstanceMsg.java` around
lines 241 - 243, The DiskAO inner class inside APICreateVmInstanceMsg defines a
cacheMode field without the same allowed-value validation as the top-level
APICreateVmInstanceMsg.cacheMode; add the same validation annotation (e.g.,
`@APIParam`(validValues = {"WriteBack"}) or the project's equivalent) to
DiskAO.cacheMode to enforce consistent valid values, or alternatively document
the allowed value in DiskAO if annotations are not used.

366-385: VolumeCacheConfig 添加 @PythonClassInventory 注解

同一文件中的 DiskAO 类已标注 @PythonClassInventory,而新增的 VolumeCacheConfig 缺少此注解。两个类均是 API 消息中的静态内部类,用于配置数据,应保持一致。建议添加该注解以确保 Python SDK 生成的完整性。

建议修改
+    `@PythonClassInventory`
     public static class VolumeCacheConfig {
         private String cachePoolUuid;
         private String cacheMode;
🤖 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/APICreateVmInstanceMsg.java` around
lines 366 - 385, VolumeCacheConfig is missing the `@PythonClassInventory`
annotation that DiskAO already has; add the `@PythonClassInventory` annotation to
the VolumeCacheConfig static inner class declaration (and import the annotation
if not present) so the Python SDK generator treats it like DiskAO; ensure the
annotation is placed immediately above "public static class VolumeCacheConfig"
and run any existing codegen/tests that validate Python inventory generation.
header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java (1)

76-94: 建议在消息层对字符串参数做 trim,降低脏输入风险

cacheMode/rootVolumeCachePoolUuid/rootVolumeCacheMode 直接透传,若上游带空白字符,后续匹配和解析容易失败。建议在 setter 中统一规整。

建议修改
 public void setCacheMode(String cacheMode) {
-    this.cacheMode = cacheMode;
+    this.cacheMode = cacheMode == null ? null : cacheMode.trim();
 }

 public void setRootVolumeCachePoolUuid(String rootVolumeCachePoolUuid) {
-    this.rootVolumeCachePoolUuid = rootVolumeCachePoolUuid;
+    this.rootVolumeCachePoolUuid = rootVolumeCachePoolUuid == null ? null : rootVolumeCachePoolUuid.trim();
 }

 public void setRootVolumeCacheMode(String rootVolumeCacheMode) {
-    this.rootVolumeCacheMode = rootVolumeCacheMode;
+    this.rootVolumeCacheMode = rootVolumeCacheMode == null ? null : rootVolumeCacheMode.trim();
 }

As per coding guidelines: “注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等”。

🤖 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/InstantiateNewCreatedVmInstanceMsg.java`
around lines 76 - 94, Update the setters to normalize incoming string parameters
by trimming whitespace: modify setCacheMode, setRootVolumeCachePoolUuid, and
setRootVolumeCacheMode to assign value == null ? null : value.trim() (i.e.,
perform null-safe trim) so that leading/trailing spaces/newlines from user input
are removed before storing in the message fields.
🤖 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.5.12__schema.sql`:
- Around line 19-35: The CREATE TABLE for VmLocalVolumeCacheVO is missing the
installPath column required by the Java VO; update the schema in
V5.5.12__schema.sql to add an installPath column (e.g. a VARCHAR with the same
length/nullability as the VO expects) to the VmLocalVolumeCacheVO definition and
ensure any ALTER/upgrade path adds the same installPath column for existing DBs
so the table structure matches the Java class VmLocalVolumeCacheVO and avoids
mapping/runtime errors.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java`:
- Around line 35-37: The code in VmLocalVolumeCachePoolInventory currently calls
JSONObjectUtil.toObject on vo.getMetadata() after only a null check, which can
throw when metadata is an empty or whitespace string; update the conditional
around inv.setMetadata to also guard against blank/empty strings (e.g., use
vo.getMetadata().trim().isEmpty() or StringUtils.isNotBlank(vo.getMetadata()))
before calling JSONObjectUtil.toObject to avoid deserialization errors.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java`:
- Around line 32-35: Update the Javadoc in VmLocalVolumeCacheVO that currently
says "in kvm plugin" to avoid the incorrect module claim: either replace that
phrase with the correct module/package that actually contains
VmLocalVolumeCachePoolVO or remove the module reference entirely so the comment
simply states it references VmLocalVolumeCachePoolVO and therefore omits an
`@ForeignKey` to avoid cross-module dependency; make this change in the comment
block that documents the nullable pool reference in VmLocalVolumeCacheVO.
- Around line 39-40: The VmLocalVolumeCacheVO entity declares a field
installPath (`@Column`(length = 2048) private String installPath;) but the
V5.5.12__schema.sql migration does not create that column, causing JPA mapping
failures; update the migration (or add a new incremental migration) to add the
installPath column to the VmLocalVolumeCacheVO table (a VARCHAR(2048) NULL
column) so the database schema matches the VmLocalVolumeCacheVO entity mapping.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java`:
- Around line 5193-5350: The new command/response classes (e.g., InitPoolCmd,
ConnectPoolCmd, ExtendPoolCmd, DeletePoolCmd, CheckPoolCmd, GetPoolCapacityCmd,
GCPoolCmd, AllocateCacheCmd, DeleteCacheCmd, FlushCacheCmd, GetCacheCapacityCmd,
AttachVolumeCacheCmd, DetachVolumeCacheCmd, and response classes like PoolRsp,
PoolHealthRsp, CacheRsp) expose required fields (poolUuid, mountPoint,
instanceUuid, volume, etc.) without null/blank validation; add validation by
annotating required fields with `@NotNull` or `@NotBlank` where appropriate (e.g.,
poolUuid, instanceUuid, volume, mountPoint) or add simple validation checks in
constructors/factory methods (throw IllegalArgumentException on missing required
values) so agents fail fast with clear errors before
serialization/deserialization.

---

Outside diff comments:
In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java`:
- Around line 74-75: The Javadoc `@summary` contains Chinese text; update the
class Javadoc for APICreateVmInstanceMsg to use English (e.g., replace "@summary
创建云主机" with a clear English summary like "@summary Create VM instance" or
"@summary Create virtual machine") ensuring the comment contains no Chinese
characters and follows existing project wording style.

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java`:
- Around line 67-89: The VolumeTO copy constructor currently shallow-copies the
cache field (this.cache = other.cache), causing two VolumeTO instances to share
a mutable CacheTO; update the copy logic to perform a deep copy: add a copy
constructor or clone method to CacheTO (e.g., CacheTO(CacheTO other) or
CacheTO.clone()) and change VolumeTO(VolumeTO other) to set this.cache =
other.cache == null ? null : new CacheTO(other.cache) (or use
other.cache.clone()), so each VolumeTO gets its own CacheTO instance;
alternatively, if sharing is intentional, add a clear comment in VolumeTO and
CacheTO documenting the shared, mutable semantics.

---

Nitpick comments:
In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java`:
- Around line 42-46: The capacity fields totalCapacity and availableCapacity in
VmLocalVolumeCachePoolVO are declared as long but arithmetic on them can
silently overflow; update any methods that modify these fields (e.g., code paths
that add or subtract capacity deltas such as capacity allocation/release
helpers) to use Math.addExact(...) and Math.subtractExact(...) (or wrap those
calls to catch ArithmeticException) when computing new totals so overflows are
detected and handled; ensure exceptions are translated to a clear error/rollback
path rather than letting incorrect values be written.

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java`:
- Around line 241-243: The DiskAO inner class inside APICreateVmInstanceMsg
defines a cacheMode field without the same allowed-value validation as the
top-level APICreateVmInstanceMsg.cacheMode; add the same validation annotation
(e.g., `@APIParam`(validValues = {"WriteBack"}) or the project's equivalent) to
DiskAO.cacheMode to enforce consistent valid values, or alternatively document
the allowed value in DiskAO if annotations are not used.
- Around line 366-385: VolumeCacheConfig is missing the `@PythonClassInventory`
annotation that DiskAO already has; add the `@PythonClassInventory` annotation to
the VolumeCacheConfig static inner class declaration (and import the annotation
if not present) so the Python SDK generator treats it like DiskAO; ensure the
annotation is placed immediately above "public static class VolumeCacheConfig"
and run any existing codegen/tests that validate Python inventory generation.

In
`@header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java`:
- Around line 76-94: Update the setters to normalize incoming string parameters
by trimming whitespace: modify setCacheMode, setRootVolumeCachePoolUuid, and
setRootVolumeCacheMode to assign value == null ? null : value.trim() (i.e.,
perform null-safe trim) so that leading/trailing spaces/newlines from user input
are removed before storing in the message fields.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java`:
- Around line 724-728: The success branch currently calls
reply.setBlockDevices(rsp.getBlockDevices()) which can propagate a null to
callers; change it to normalize null to an empty collection (e.g. if
rsp.getBlockDevices() == null then set an empty List) before calling
reply.setBlockDevices so callers always receive a non-null list; update the
success branch in the method where reply.setBlockDevices and rsp.getBlockDevices
are used (KVMHost related handling).

ℹ️ Review info

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7f8bd3 and 959fe16.

⛔ Files ignored due to path filters (59)
  • conf/globalConfig/kvm.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCache.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/springConfigXml/Kvm.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
  • conf/springConfigXml/localVolumeCache.xml is excluded by !**/*.xml
  • conf/zstack.xml is excluded by !**/*.xml
  • sdk/src/main/java/SourceClassMap.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ApplicationDevelopmentServiceInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CacheTO.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateSchedulerJobAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateSchedulerJobGroupAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployAppDevelopmentServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployModelEvalServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployModelServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetMdevDeviceCandidatesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GpuAllocateStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GpuDeviceSpecInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/MatchModelServiceTemplateWithModelAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/MdevDeviceSpecInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ModelService.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ModelServiceInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateMdevDeviceSpecAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateModelServiceInstanceGroupAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdatePciDeviceSpecAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeBackupInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeTO.java is excluded by !sdk/**
📒 Files selected for processing (38)
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
💤 Files with no reviewable changes (2)
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
🚧 Files skipped from review as they are similar to previous changes (7)
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java

Comment on lines +35 to +37
if (vo.getMetadata() != null) {
inv.setMetadata(JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class));
}
Copy link

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:

# 查找目标文件及其上下文
fd 'VmLocalVolumeCachePoolInventory.java' -t f --exec cat -n {} \;

Repository: MatheMatrix/zstack

Length of output: 8214


🏁 Script executed:

# 查找 metadata 的写入路径和 JSONObjectUtil 的使用方式
rg -n 'setMetadata|metadata.*=' --type java -B 2 -A 2 | head -60

Repository: MatheMatrix/zstack

Length of output: 5621


🏁 Script executed:

# 查找数据库 schema 中 VmLocalVolumeCachePool 的定义
fd '__schema\.sql$|migration.*sql$' -t f --exec grep -l 'VmLocalVolumeCachePool' {} \;

Repository: MatheMatrix/zstack

Length of output: 99


🏁 Script executed:

# 查找 JSONObjectUtil.toObject 的实现和使用示例
rg 'JSONObjectUtil\.toObject' --type java -B 1 -A 1 | head -40

Repository: MatheMatrix/zstack

Length of output: 3971


🏁 Script executed:

# 检查类似的 metadata 处理模式
rg 'toObject.*metadata|metadata.*toObject' --type java -B 2 -A 2

Repository: MatheMatrix/zstack

Length of output: 2593


🏁 Script executed:

# 查找 JSONObjectUtil 的实现
fd 'JSONObjectUtil.java' -t f --exec cat {} \;

Repository: MatheMatrix/zstack

Length of output: 4412


🏁 Script executed:

# 查找 VmLocalVolumeCachePoolVO 的定义和写入逻辑
fd 'VmLocalVolumeCachePoolVO.java' -t f --exec cat {} \;

Repository: MatheMatrix/zstack

Length of output: 3598


🏁 Script executed:

# 查找该新增文件是否在 PR 中有相关的写入/保存逻辑
rg 'VmLocalVolumeCachePool.*DAO|VmLocalVolumeCachePoolVO.*save|VmLocalVolumeCachePoolVO.*insert' --type java -B 2 -A 5

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查找其他 toObject 对 metadata 的使用,检查是否存在 blank 检查的最佳实践
rg 'toObject.*metadata|metadata.*toObject' --type java -B 3 -A 3 | head -80

Repository: MatheMatrix/zstack

Length of output: 3442


🏁 Script executed:

# 验证 JSONUtil 在其他代码中是否有类似的 blank 防护
rg 'StringUtils\.isBlank\(.*metadata\)|StringUtils\.isEmpty\(.*metadata\)' --type java -B 2 -A 2

Repository: MatheMatrix/zstack

Length of output: 44


在反序列化 metadata 之前需要检查空字符串

当前代码仅判断 null,但若 metadata 为空字符串或纯空白字符,JSONObjectUtil.toObject 会触发 Gson 反序列化异常。建议在调用前添加 blank 判断:

if (vo.getMetadata() != null && !vo.getMetadata().trim().isEmpty()) {
    inv.setMetadata(JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class));
}

或使用 StringUtils.isNotBlank(vo.getMetadata())

🤖 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/localVolumeCache/VmLocalVolumeCachePoolInventory.java`
around lines 35 - 37, The code in VmLocalVolumeCachePoolInventory currently
calls JSONObjectUtil.toObject on vo.getMetadata() after only a null check, which
can throw when metadata is an empty or whitespace string; update the conditional
around inv.setMetadata to also guard against blank/empty strings (e.g., use
vo.getMetadata().trim().isEmpty() or StringUtils.isNotBlank(vo.getMetadata()))
before calling JSONObjectUtil.toObject to avoid deserialization errors.

Comment on lines +5193 to +5350
public static class InitPoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
public List<String> pvs;
}

public static class ConnectPoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
}

public static class ExtendPoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
public List<String> pvs;
}

public static class DeletePoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
}

public static class CheckPoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
}

public static class GetPoolCapacityCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
}

public static class GCPoolCmd extends AgentCommand {
public String poolUuid;
public String mountPoint;
public boolean force;
public List<VolumeTO> volumes;
}

public static class AllocateCacheCmd extends AgentCommand {
public String poolUuid;
public VolumeTO volume;
}

public static class DeleteCacheCmd extends AgentCommand {
public String poolUuid;
public VolumeTO volume;
}

public static class FlushCacheCmd extends AgentCommand {
public String poolUuid;
public VolumeTO volume;
}

public static class GetCacheCapacityCmd extends AgentCommand {
public String poolUuid;
public VolumeTO volume;
}

public static class AttachVolumeCacheCmd extends AgentCommand {
public String instanceUuid;
public VolumeTO volume;
}

public static class DetachVolumeCacheCmd extends AgentCommand implements HasThreadContext {
public String instanceUuid;
public VolumeTO volume;
}

// ========================================================================
// Local Volume Cache — Ref Structures
// ========================================================================

public static class PVRef {
public String pvUuid;
public String pvName;
public String pvDevicePath;
}

public static class PVHealthRef extends PVRef {
public Boolean healthy;
}

public static class VGRef {
public String vgUuid;
public String vgName;
}

public static class LVRef {
public String lvUuid;
public String lvName;
public String lvPath;
}

public static class FileSystemRef {
public String fsUuid;
public String fsType;
}

public static class VolumeRef {
public String volumeUuid;
public String installPath;
public String deviceType;
public String format;
public Long size;
}

// ========================================================================
// Local Volume Cache — Response Definitions
// ========================================================================

public static class PoolRsp extends AgentResponse {
public String poolUuid;
public String mountPoint;
public List<PVRef> pvs;
public VGRef vg;
public LVRef lv;
public FileSystemRef filesystem;
}

public static class PoolHealthRsp extends AgentResponse {
public Boolean healthy;
public List<PVHealthRef> pvs;
public Boolean vg;
public Boolean lv;
public Boolean filesystem;
}

public static class PoolCapacityRsp extends AgentResponse {
public Long total;
public Long used;
public Long available;
public Long allocated;
public Long dirty;
}

public static class CacheRsp extends AgentResponse {
public String installPath;
public Long virtualSize;
public Long actualSize;
}

public static class GCPoolRsp extends AgentResponse {
public List<String> gcFiles;
public Integer gcCount;
}

public static class AttachVolumeCacheRsp extends AgentResponse {
}

public static class DetachVolumeCacheRsp extends AgentResponse {
}
Copy link

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:

# 首先检查文件是否存在以及获取其基本信息
ls -lh plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
wc -l plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java

Repository: MatheMatrix/zstack

Length of output: 239


🏁 Script executed:

# 检查这些命令对象在文件中的构造位置
rg -n "new.*InitPoolCmd|new.*ConnectPoolCmd|new.*ExtendPoolCmd|new.*DeletePoolCmd|new.*CheckPoolCmd|new.*GetPoolCapacityCmd|new.*GCPoolCmd|new.*AllocateCacheCmd|new.*DeleteCacheCmd|new.*FlushCacheCmd|new.*GetCacheCapacityCmd|new.*AttachVolumeCacheCmd|new.*DetachVolumeCacheCmd" plugin/kvm/src/main/java/org/zstack/kvm/

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 检查同文件中其他 DTO 的访问风格(查找其他继承 AgentCommand 或 AgentResponse 的类)
rg -n "extends AgentCommand\|extends AgentResponse" plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查看这些新增 DTO 之前的代码样式(查看行号 5100-5193 之间的内容)
sed -n '5100,5193p' plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | tail -50

Repository: MatheMatrix/zstack

Length of output: 1421


🏁 Script executed:

# 检查这些命令是否在其他文件中被构造使用
rg -l "InitPoolCmd|ConnectPoolCmd|ExtendPoolCmd|DeletePoolCmd" plugin/kvm/src --type java | grep -v KVMAgentCommands.java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 获取行号 5193-5350 的完整代码
sed -n '5193,5350p' plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | head -100

Repository: MatheMatrix/zstack

Length of output: 2823


🏁 Script executed:

# 搜索这些类在整个 kvm 目录中的使用(不仅是构造)
rg "InitPoolCmd|ConnectPoolCmd|ExtendPoolCmd" plugin/kvm/src --type java -A 2 | head -50

Repository: MatheMatrix/zstack

Length of output: 1005


🏁 Script executed:

# 查看 AgentCommand 基类的定义
rg -n "class AgentCommand" plugin/kvm/src --type java -A 5

Repository: MatheMatrix/zstack

Length of output: 667


🏁 Script executed:

# 检查文件中其他 DTO 的访问模式,看是否存在两种混合使用的情况
sed -n '1,500p' plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | grep -E "public (String|List|Boolean|Integer|Long|VolumeTO)" | head -20

Repository: MatheMatrix/zstack

Length of output: 891


🏁 Script executed:

# 查看整个文件的访问风格统计(public 字段 vs private+getter/setter)
grep -n "public String\|public List\|public Boolean\|public Integer\|public Long" plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | wc -l
grep -n "private String\|private List\|private Boolean\|private Integer\|private Long" plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | wc -l

Repository: MatheMatrix/zstack

Length of output: 69


🏁 Script executed:

# 查看更多 AgentCommand 的子类实现,了解整体风格
sed -n '70,500p' plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | grep -E "class.*extends AgentCommand" -A 3 | head -30

Repository: MatheMatrix/zstack

Length of output: 1149


🏁 Script executed:

# 检查是否有地方对这些字段进行了验证或非空检查
rg "poolUuid|instanceUuid" plugin/kvm/src --type java -B 2 -A 2 | grep -E "Objects.requireNonNull|if.*==|assert" | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

# 查看 Response 类是否也有相同的访问风格
sed -n '5250,5350p' plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java

Repository: MatheMatrix/zstack

Length of output: 2871


🏁 Script executed:

# 检查这些命令类在何处被创建或序列化
rg "InitPoolCmd\|PoolRsp" plugin/kvm/src/main/java --type java | head -20

Repository: MatheMatrix/zstack

Length of output: 44


新增命令/响应类缺少关键字段的非空验证。

行 5193-5350 新增的 Command、Reference 和 Response 类(如 InitPoolCmdAttachVolumeCacheCmdPoolRsp 等)都使用 public 字段直接暴露。虽然这与文件中 AgentCommand 基类及其他大多数子类的风格一致,但这些必填字段(poolUuidinstanceUuidvolume 等)缺少非空校验,容易因上游未正确初始化而导致后续 agent 端晚失败。建议至少对必填字段添加 @NotNull@NotBlank 注解,或在构造/序列化前进行显式验证。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines
5193 - 5350, The new command/response classes (e.g., InitPoolCmd,
ConnectPoolCmd, ExtendPoolCmd, DeletePoolCmd, CheckPoolCmd, GetPoolCapacityCmd,
GCPoolCmd, AllocateCacheCmd, DeleteCacheCmd, FlushCacheCmd, GetCacheCapacityCmd,
AttachVolumeCacheCmd, DetachVolumeCacheCmd, and response classes like PoolRsp,
PoolHealthRsp, CacheRsp) expose required fields (poolUuid, mountPoint,
instanceUuid, volume, etc.) without null/blank validation; add validation by
annotating required fields with `@NotNull` or `@NotBlank` where appropriate (e.g.,
poolUuid, instanceUuid, volume, mountPoint) or add simple validation checks in
constructors/factory methods (throw IllegalArgumentException on missing required
values) so agents fail fast with clear errors before
serialization/deserialization.

@MatheMatrix MatheMatrix force-pushed the sync/haidong.pang/feature/ZSTAC-81715 branch from 959fe16 to e037533 Compare March 4, 2026 10:57
Copy link

@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.

Caution

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

⚠️ Outside diff range comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java (1)

67-89: ⚠️ Potential issue | 🟠 Major

拷贝构造函数对 cache 使用浅拷贝,存在状态串扰风险

Line 88 直接执行 this.cache = other.cache;,会让两个 VolumeTO 实例共享同一个 CacheTO 引用。后续任一处修改 cache 都可能污染另一个对象,建议改为深拷贝(或显式复制字段)。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java` around lines 67 - 89,
The VolumeTO copy constructor currently assigns the cache field by reference
(this.cache = other.cache) which causes shared mutable state; modify the
VolumeTO(VolumeTO other) constructor to deep-copy the CacheTO by creating a new
CacheTO instance (or invoking its copy/clone method) and assigning that to
this.cache, or explicitly copy CacheTO's fields into a new object so each
VolumeTO has an independent cache; update any constructor calls or helpers
accordingly and ensure CacheTO's copy mechanism (e.g., a copy constructor or
clone method) is used if available.
♻️ Duplicate comments (2)
plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java (1)

43-49: ⚠️ Potential issue | 🟠 Major

补齐 valueOf 的空值防护,避免运行时 NPE

当前映射在 invcacheMode 为空时会直接中断,影响缓存信息下发流程。

建议修复
 public static CacheTO valueOf(VmLocalVolumeCacheInventory inv) {
+    if (inv == null) {
+        return null;
+    }
     CacheTO to = new CacheTO();
     to.setCacheUuid(inv.getUuid());
     to.setPoolUuid(inv.getPoolUuid());
     to.setInstallPath(inv.getInstallPath());
-    to.setCacheMode(inv.getCacheMode().name());
+    to.setCacheMode(inv.getCacheMode() == null ? null : inv.getCacheMode().name());
     return to;
 }

As per coding guidelines "向后兼容原则:之前的代码产生的行为不要直接去改动... 应当对原状态做好记录,做好二次确认,做好回退准备"。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java` around lines 43 - 49,
In CacheTO.valueOf, add null checks to prevent NPEs: if the input
VmLocalVolumeCacheInventory inv is null, return null immediately; otherwise set
cacheUuid, poolUuid and installPath from inv as before but guard cacheMode by
checking inv.getCacheMode() != null — call
setCacheMode(inv.getCacheMode().name()) only when non-null and
setCacheMode(null) or leave it unset when cacheMode is null so no .name() is
invoked; reference the CacheTO.valueOf method and
VmLocalVolumeCacheInventory.getCacheMode()/getUuid()/getPoolUuid()/getInstallPath()
when applying these checks.
header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java (1)

22-34: ⚠️ Potential issue | 🟠 Major

修正 equals/hashCodewwid 为空时不能把所有设备都视为同一个

当前实现会把 wwid == null 的设备全部判等,可能导致设备列表被错误去重。

建议修复
+import java.util.Objects;
@@
     `@Override`
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;

         HostBlockDeviceStruct that = (HostBlockDeviceStruct) o;
-        return wwid != null ? wwid.equals(that.wwid) : that.wwid == null;
+        if (wwid != null && that.wwid != null) {
+            return wwid.equals(that.wwid);
+        }
+        return Objects.equals(path, that.path) && Objects.equals(hctl, that.hctl);
     }

     `@Override`
     public int hashCode() {
-        return wwid != null ? wwid.hashCode() : 0;
+        return wwid != null ? wwid.hashCode() : Objects.hash(path, hctl);
     }
🤖 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/host/HostBlockDeviceStruct.java`
around lines 22 - 34, The current equals/hashCode treat two
HostBlockDeviceStruct instances with null wwid as equal; change
HostBlockDeviceStruct.equals to return true only for same reference when wwid is
null (i.e., if (this==o) true; if (o==null || getClass()!=o.getClass()) false;
cast to HostBlockDeviceStruct then if (this.wwid == null || that.wwid == null)
return false; return this.wwid.equals(that.wwid);), and update hashCode to
return wwid != null ? wwid.hashCode() : System.identityHashCode(this) so
hashCode is consistent with the new equals behavior.
🧹 Nitpick comments (4)
compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java (1)

405-434: 建议把“缓存启用失败”提升为可观测结果,而不只写日志。

当前降级策略是继续流程,但调用链上层无法直接感知该盘缓存未生效。建议记录失败卷并透传到流程上下文/事件,便于排障与运维审计。

🤖 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/VmInstantiateOtherDiskFlow.java`
around lines 405 - 434, The flow currently swallows cache-enable failures
(inside the NoRollbackFlow anonymous flow in VmInstantiateOtherDiskFlow) by only
logging them; change it to record failures into the flow context so upstream
callers can observe them: when EnableVolumeCacheMsg reply.isSuccess() is false,
add an entry (e.g. append volumeInventory.getUuid() plus reply.getError() to a
List/Map stored in the flow data/context under a well-known key like
"failedCacheVolumes" or "volumeCacheEnableFailures") before calling
innerTrigger.next(); ensure the data structure is created if absent and use the
same key so later flows or the outer caller can read the failures and/or emit an
event.
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java (2)

215-223: 建议收敛 cacheModerootVolumeCacheMode 的语义边界。

当前两者都表达根盘缓存模式,建议尽早明确优先级或收敛为单一来源,避免后续新增模式时出现配置分歧。

Also applies to: 635-657

🤖 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/APICreateVmInstanceMsg.java` around
lines 215 - 223, The two fields cacheMode and rootVolumeCacheMode in
APICreateVmInstanceMsg both represent root disk cache mode and must be
reconciled: pick a single canonical field (preferably rootVolumeCacheMode) or
define a clear precedence rule (e.g., rootVolumeCacheMode overrides cacheMode)
and implement it everywhere; update the APIParam annotations and validation
logic around cacheMode and rootVolumeCacheMode, add deprecation notes/compat
handling if you keep both (map legacy cacheMode into rootVolumeCacheMode at
message construction), and adjust any consumers that read either field so they
consistently use the chosen canonical value.

667-679: 建议在 __example__ 中补充缓存配置示例。

新增 API 字段未在示例中体现,会降低调用方对新能力的可发现性。

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

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java` around
lines 667 - 679, Update the APICreateVmInstanceMsg.__example__ to include a
representative cache configuration so the new API fields are discoverable;
inside the method (where msg is built and methods like
msg.setRootVolumeSystemTags(...) and msg.setDataVolumeSystemTags(...) are
called) add example calls to the new cache-related setters (e.g., the cache
configuration setters added to APICreateVmInstanceMsg such as setCacheMode /
setDiskCacheMode / setVolumeCachePolicy or whatever exact field names were
introduced) with realistic sample values so callers can see how to
enable/configure caching when creating a VM.
compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java (1)

150-154: 可选:抽取公共方法减少重复赋值代码。

两处缓存字段赋值完全同构,建议抽成私有 helper,后续改字段时不易漏改。

Also applies to: 174-178

🤖 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/InstantiateVmFromNewCreatedStruct.java`
around lines 150 - 154, Extract the repeated cache-field assignments into a
private helper to avoid duplication: create a method (e.g.
applyCacheFieldsToStruct or copyCacheConfig) that accepts the target struct
(InstantiateVmFromNewCreatedStruct or whatever type of "struct") and the source
message/object (the msg being used) and moves the five setters
(setEnableRootVolumeCache, setCacheMode, setRootVolumeCachePoolUuid,
setRootVolumeCacheMode, setDataDiskCacheConfigOnIndex) into that helper; then
replace both original code blocks (the lines around setEnableRootVolumeCache..
at 150-154 and the same block at 174-178) with calls to this new helper so
future changes to those fields only need to be made in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java`:
- Around line 67-89: The VolumeTO copy constructor currently assigns the cache
field by reference (this.cache = other.cache) which causes shared mutable state;
modify the VolumeTO(VolumeTO other) constructor to deep-copy the CacheTO by
creating a new CacheTO instance (or invoking its copy/clone method) and
assigning that to this.cache, or explicitly copy CacheTO's fields into a new
object so each VolumeTO has an independent cache; update any constructor calls
or helpers accordingly and ensure CacheTO's copy mechanism (e.g., a copy
constructor or clone method) is used if available.

---

Duplicate comments:
In `@header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java`:
- Around line 22-34: The current equals/hashCode treat two HostBlockDeviceStruct
instances with null wwid as equal; change HostBlockDeviceStruct.equals to return
true only for same reference when wwid is null (i.e., if (this==o) true; if
(o==null || getClass()!=o.getClass()) false; cast to HostBlockDeviceStruct then
if (this.wwid == null || that.wwid == null) return false; return
this.wwid.equals(that.wwid);), and update hashCode to return wwid != null ?
wwid.hashCode() : System.identityHashCode(this) so hashCode is consistent with
the new equals behavior.

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java`:
- Around line 43-49: In CacheTO.valueOf, add null checks to prevent NPEs: if the
input VmLocalVolumeCacheInventory inv is null, return null immediately;
otherwise set cacheUuid, poolUuid and installPath from inv as before but guard
cacheMode by checking inv.getCacheMode() != null — call
setCacheMode(inv.getCacheMode().name()) only when non-null and
setCacheMode(null) or leave it unset when cacheMode is null so no .name() is
invoked; reference the CacheTO.valueOf method and
VmLocalVolumeCacheInventory.getCacheMode()/getUuid()/getPoolUuid()/getInstallPath()
when applying these checks.

---

Nitpick comments:
In
`@compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java`:
- Around line 150-154: Extract the repeated cache-field assignments into a
private helper to avoid duplication: create a method (e.g.
applyCacheFieldsToStruct or copyCacheConfig) that accepts the target struct
(InstantiateVmFromNewCreatedStruct or whatever type of "struct") and the source
message/object (the msg being used) and moves the five setters
(setEnableRootVolumeCache, setCacheMode, setRootVolumeCachePoolUuid,
setRootVolumeCacheMode, setDataDiskCacheConfigOnIndex) into that helper; then
replace both original code blocks (the lines around setEnableRootVolumeCache..
at 150-154 and the same block at 174-178) with calls to this new helper so
future changes to those fields only need to be made in one place.

In `@compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java`:
- Around line 405-434: The flow currently swallows cache-enable failures (inside
the NoRollbackFlow anonymous flow in VmInstantiateOtherDiskFlow) by only logging
them; change it to record failures into the flow context so upstream callers can
observe them: when EnableVolumeCacheMsg reply.isSuccess() is false, add an entry
(e.g. append volumeInventory.getUuid() plus reply.getError() to a List/Map
stored in the flow data/context under a well-known key like "failedCacheVolumes"
or "volumeCacheEnableFailures") before calling innerTrigger.next(); ensure the
data structure is created if absent and use the same key so later flows or the
outer caller can read the failures and/or emit an event.

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java`:
- Around line 215-223: The two fields cacheMode and rootVolumeCacheMode in
APICreateVmInstanceMsg both represent root disk cache mode and must be
reconciled: pick a single canonical field (preferably rootVolumeCacheMode) or
define a clear precedence rule (e.g., rootVolumeCacheMode overrides cacheMode)
and implement it everywhere; update the APIParam annotations and validation
logic around cacheMode and rootVolumeCacheMode, add deprecation notes/compat
handling if you keep both (map legacy cacheMode into rootVolumeCacheMode at
message construction), and adjust any consumers that read either field so they
consistently use the chosen canonical value.
- Around line 667-679: Update the APICreateVmInstanceMsg.__example__ to include
a representative cache configuration so the new API fields are discoverable;
inside the method (where msg is built and methods like
msg.setRootVolumeSystemTags(...) and msg.setDataVolumeSystemTags(...) are
called) add example calls to the new cache-related setters (e.g., the cache
configuration setters added to APICreateVmInstanceMsg such as setCacheMode /
setDiskCacheMode / setVolumeCachePolicy or whatever exact field names were
introduced) with realistic sample values so callers can see how to
enable/configure caching when creating a VM.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: da8e7de6-017b-474a-80f2-ed1b38cb4ce8

📥 Commits

Reviewing files that changed from the base of the PR and between 959fe16 and e037533.

⛔ Files ignored due to path filters (58)
  • conf/globalConfig/kvm.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCache.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
  • conf/springConfigXml/localVolumeCache.xml is excluded by !**/*.xml
  • conf/zstack.xml is excluded by !**/*.xml
  • sdk/src/main/java/SourceClassMap.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ApplicationDevelopmentServiceInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CacheTO.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateSchedulerJobAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateSchedulerJobGroupAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployAppDevelopmentServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployModelEvalServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeployModelServiceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetMdevDeviceCandidatesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GpuAllocateStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GpuDeviceSpecInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/MatchModelServiceTemplateWithModelAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/MdevDeviceSpecInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ModelService.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ModelServiceInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateMdevDeviceSpecAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateModelServiceInstanceGroupAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdatePciDeviceSpecAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeBackupInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeTO.java is excluded by !sdk/**
📒 Files selected for processing (38)
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
💤 Files with no reviewable changes (2)
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
🚧 Files skipped from review as they are similar to previous changes (11)
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java

@MatheMatrix MatheMatrix force-pushed the sync/haidong.pang/feature/ZSTAC-81715 branch from e037533 to 49191cf Compare March 9, 2026 08:29
Copy link

@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: 10

♻️ Duplicate comments (6)
plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java (1)

43-49: ⚠️ Potential issue | 🟠 Major

cacheMode 仍然缺少空值保护。

Line 48 直接调用 inv.getCacheMode().name(),旧数据或异常数据里只要 cacheModenull,这里就会把整个下发流程打断。这个问题在前一轮已经提过了,当前版本还在。

建议修改
 public static CacheTO valueOf(VmLocalVolumeCacheInventory inv) {
+    if (inv == null) {
+        return null;
+    }
     CacheTO to = new CacheTO();
     to.setCacheUuid(inv.getUuid());
     to.setPoolUuid(inv.getPoolUuid());
     to.setInstallPath(inv.getInstallPath());
-    to.setCacheMode(inv.getCacheMode().name());
+    to.setCacheMode(inv.getCacheMode() == null ? null : inv.getCacheMode().name());
     return to;
 }

As per coding guidelines “向后兼容原则:之前的代码产生的行为不要直接去改动,即使是 bug 也可能用户有依赖,不应直接改动,可以考虑升级用户一套行为、新安装用户一套行为,开关控制等”。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java` around lines 43 - 49,
In CacheTO.valueOf, guard against a null cacheMode on
VmLocalVolumeCacheInventory by checking inv.getCacheMode() before calling
.name(); call to.setCacheMode(inv.getCacheMode().name()) only when
getCacheMode() != null, otherwise set a safe value (e.g. null or empty string)
to avoid NullPointerException while preserving backward compatibility; locate
the logic in CacheTO.valueOf and the interactions with
VmLocalVolumeCacheInventory.getCacheMode and CacheTO.setCacheMode to implement
this null-safe assignment.
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java (2)

35-36: ⚠️ Potential issue | 🔴 Critical

实体和升级脚本仍然不一致:installPath 对应列缺失。

这里已经把 installPath 映射成持久化字段了,但本 PR 的升级脚本还没有给 VmLocalVolumeCacheVO 表补这列。这样一启动 Hibernate/JPA 就会按实体去找不存在的列,属于上线阻塞项。

建议在升级脚本里补列
+ALTER TABLE `zstack`.`VmLocalVolumeCacheVO`
+ADD COLUMN `installPath` VARCHAR(2048) DEFAULT NULL;
🤖 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/localVolumeCache/VmLocalVolumeCacheVO.java`
around lines 35 - 36, The entity VmLocalVolumeCacheVO now declares a persistent
field installPath (`@Column`(length = 2048)) but the DB upgrade script did not add
the corresponding column, causing Hibernate/JPA to fail at startup; fix by
updating the migration/upgrade script to ALTER TABLE the VmLocalVolumeCacheVO
backing table to add an installPath column that matches the entity
(varchar/character length 2048, nullable if the entity allows nulls) and include
this migration in the same upgrade sequence so the schema and the
VmLocalVolumeCacheVO.installPath mapping stay consistent.

28-31: ⚠️ Potential issue | 🟡 Minor

注释里的模块归属还是写错了。

这里写的是 VmLocalVolumeCachePoolVO in kvm plugin,但实际类型在 org.zstack.header.localVolumeCache,不是 kvm plugin。注释继续保留这个说法会误导后续维护者判断依赖边界。

As per coding guidelines “对于较长的注释,需要仔细校对并随代码更新,确保内容正确”。

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

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java`
around lines 28 - 31, The Javadoc on VmLocalVolumeCacheVO incorrectly states
"VmLocalVolumeCachePoolVO in kvm plugin"; update the comment on
VmLocalVolumeCacheVO to reference the correct package/module
(org.zstack.header.localVolumeCache) and clarify the intent (e.g., "References
VmLocalVolumeCachePoolVO in org.zstack.header.localVolumeCache — no `@ForeignKey`
to avoid cross-module dependency") so the module ownership and rationale remain
accurate; ensure the symbol VmLocalVolumeCachePoolVO and the note about avoiding
`@ForeignKey` are preserved and worded correctly.
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java (1)

33-35: ⚠️ Potential issue | 🟡 Minor

反序列化 metadata 前要先排除空白串。

这里只判空不够;只要库里落了 "" 或全空白字符串,JSONObjectUtil.toObject(...) 就可能在 inventory 转换时抛异常,连带影响池信息展开。

建议修改
-        if (vo.getMetadata() != null) {
+        if (vo.getMetadata() != null && !vo.getMetadata().trim().isEmpty()) {
             inv.setMetadata(JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.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/localVolumeCache/VmLocalVolumeCachePoolInventory.java`
around lines 33 - 35, The metadata deserialization can throw if metadata is an
empty or whitespace-only string; update the check in
VmLocalVolumeCachePoolInventory (where vo.getMetadata() is used) to ensure you
only call JSONObjectUtil.toObject(...) when vo.getMetadata() != null &&
vo.getMetadata().trim().length() > 0 (or use
!vo.getMetadata().trim().isEmpty()), so blank strings are treated as absent and
skipped before deserializing.
header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java (1)

22-33: ⚠️ Potential issue | 🟠 Major

wwid == null 时会把不同块设备错误地判成同一个对象。

当前 equals/hashCode 只看 wwid;一旦 agent 返回多个 wwid 为空的设备,集合去重或比对时会把真实设备条目吞掉。建议在 wwid 为空时回退到稳定次键(例如 path + hctl),并保证 equalshashCode 使用同一套规则。

建议修改
+import java.util.Objects;
@@
     `@Override`
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
         HostBlockDeviceStruct that = (HostBlockDeviceStruct) o;
-        return wwid != null ? wwid.equals(that.wwid) : that.wwid == null;
+        if (wwid != null && that.wwid != null) {
+            return wwid.equals(that.wwid);
+        }
+        return Objects.equals(path, that.path) && Objects.equals(hctl, that.hctl);
     }
 
     `@Override`
     public int hashCode() {
-        return wwid != null ? wwid.hashCode() : 0;
+        return wwid != null ? wwid.hashCode() : Objects.hash(path, hctl);
     }
🤖 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/host/HostBlockDeviceStruct.java`
around lines 22 - 33, The equals/hashCode currently only consider wwid in
HostBlockDeviceStruct, causing different devices with null wwid to be treated
equal; update equals(Object) to, when wwid is non-null, compare by wwid, and
when wwid is null, fall back to comparing a stable secondary key (e.g. path and
hctl fields) and update hashCode() to return a consistent hash based on the same
branch (wwid.hashCode() when present, otherwise Objects.hash(path, hctl) or
equivalent) so both equals and hashCode use the identical decision logic.
plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java (1)

5193-5266: ⚠️ Potential issue | 🟡 Minor

这些 agent 命令的必填字段仍然缺少前置校验。

poolUuidmountPointinstanceUuidvolume 等字段现在全靠上游自觉填充;一旦缺值,失败只会延后到 agent 侧,报错定位也更差。既然这个文件已经大量使用 public-field DTO,至少在命令下发前补一个轻量校验入口,避免把空命令发下去。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines
5193 - 5266, Add lightweight pre-send validation to these agent command DTOs by
introducing a validate() contract on AgentCommand (or overrideable method) and
implement it in the specific commands to assert required fields are
non-null/non-empty; e.g., in
InitPoolCmd/ConnectPoolCmd/ExtendPoolCmd/DeletePoolCmd/CheckPoolCmd/GetPoolCapacityCmd/GCPoolCmd
check poolUuid and mountPoint (and volumes for GCPoolCmd), in
AllocateCacheCmd/DeleteCacheCmd/FlushCacheCmd/GetCacheCapacityCmd ensure
poolUuid and volume are present, and in
AttachVolumeCacheCmd/DetachVolumeCacheCmd ensure instanceUuid and volume are
present; have validate() throw a clear IllegalArgumentException (or a checked
validation exception) with which-field-is-missing so callers can reject requests
before sending to the agent.
🧹 Nitpick comments (5)
compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java (2)

404-434: 缓存启用逻辑合理,但可考虑记录更多上下文信息。

当前实现在缓存启用失败时仅记录警告日志并继续流程,这符合非阻塞设计。建议在日志中补充更多上下文信息,如 poolUuidcacheMode,便于问题排查。

♻️ 建议改进日志
                                 if (!reply.isSuccess()) {
-                                        logger.warn(String.format("failed to enable cache for DiskAO volume[uuid:%s]: %s",
-                                                volumeInventory.getUuid(), reply.getError()));
+                                        logger.warn(String.format("failed to enable cache for DiskAO volume[uuid:%s, poolUuid:%s, cacheMode:%s]: %s",
+                                                volumeInventory.getUuid(), diskAO.getCachePoolUuid(), diskAO.getCacheMode(), reply.getError()));
                                     }
🤖 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/VmInstantiateOtherDiskFlow.java`
around lines 404 - 434, The warning log inside VmInstantiateOtherDiskFlow's
NoRollbackFlow when enabling cache (in the run override that sends
EnableVolumeCacheMsg) lacks useful context; update the logger.warn call to
include the pool UUID and cache mode (use diskAO.getCachePoolUuid() and
diskAO.getCacheMode()) along with the volume UUID and reply.getError(), so the
message from the EnableVolumeCacheMsg callback contains
volumeInventory.getUuid(), diskAO.getCachePoolUuid(), diskAO.getCacheMode(), and
reply.getError() while preserving the current non-failing behavior.

35-35: 未使用的导入。

java.sql.Array 导入未在文件中使用,应移除。

♻️ 建议修复
-import java.sql.Array;
🤖 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/VmInstantiateOtherDiskFlow.java`
at line 35, Remove the unused import java.sql.Array from the top of
VmInstantiateOtherDiskFlow (the import statement referencing java.sql.Array) —
locate the import in VmInstantiateOtherDiskFlow and delete that line so there
are no unused imports remaining.
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsgDoc_zh_cn.groovy (1)

332-378: 新增参数缺少描述文档。

所有新增的缓存相关参数(enableRootVolumeCachecacheModerootVolumeCachePoolUuidrootVolumeCacheModedataDiskCacheConfigOnIndex)的 desc 字段均为空字符串。建议补充中文描述,以便用户理解这些参数的用途,保持与其他参数文档的一致性。

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

In
`@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsgDoc_zh_cn.groovy`
around lines 332 - 378, The new cache-related API parameters
(enableRootVolumeCache, cacheMode, rootVolumeCachePoolUuid, rootVolumeCacheMode,
dataDiskCacheConfigOnIndex) have empty desc fields; update the column blocks in
APICreateVmInstanceMsgDoc_zh_cn.groovy to provide concise Chinese descriptions
for each parameter explaining purpose and accepted values (e.g.,
enableRootVolumeCache: whether to enable root volume caching;
cacheMode/rootVolumeCacheMode: cache strategy such as "WriteBack";
rootVolumeCachePoolUuid: UUID of the cache pool to use;
dataDiskCacheConfigOnIndex: map of per-disk cache index configurations), keeping
the existing enclosedIn, location, type, optional and since fields unchanged and
matching wording style used by neighboring parameter docs.
header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy (1)

6-17: 文档标题为占位符,字段描述不正确。

  1. Line 8: 标题 "在这里输入结构的名称" 是占位符,应替换为实际的结构名称(如 "获取块设备响应")。
  2. Line 13: blockDevicesdesc 为字符串 "null",这看起来不正确。应提供有意义的描述或使用空字符串 ""
🤖 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/host/APIGetBlockDevicesEventDoc_zh_cn.groovy`
around lines 6 - 17, Replace the placeholder title and incorrect description in
the doc block: update the doc.title (currently "在这里输入结构的名称") to a meaningful
title like "获取块设备响应" and change the ref for blockDevices (ref.name
"blockDevices", ref.path
"org.zstack.header.host.APIGetBlockDevicesEvent.blockDevices") so its desc is a
proper description (e.g., "主机上的块设备列表") or an empty string "" instead of the
literal "null"; make these edits inside the doc { ... } block in
APIGetBlockDevicesEventDoc_zh_cn.groovy where clz HostBlockDeviceStruct.class is
referenced.
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy (1)

5-27: 文档标题为占位符,枚举值描述缺失。

  1. Line 7: 标题 "在这里输入结构的名称" 是占位符,建议替换为 "本地卷缓存池状态" 或类似描述。
  2. 各枚举值(ConnectingConnectedDisconnected)的 desc 字段为空,建议补充描述以说明各状态的含义。
♻️ 建议补充描述
doc {
	title "本地卷缓存池状态"

	field {
		name "Connecting"
		desc "缓存池正在连接中"
		type "VmLocalVolumeCachePoolStatus"
		since "5.5.6"
	}
	field {
		name "Connected"
		desc "缓存池已连接"
		type "VmLocalVolumeCachePoolStatus"
		since "5.5.6"
	}
	field {
		name "Disconnected"
		desc "缓存池已断开连接"
		type "VmLocalVolumeCachePoolStatus"
		since "5.5.6"
	}
}
🤖 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/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy`
around lines 5 - 27, Replace the placeholder title and fill missing enum
descriptions in the Groovy doc: update the doc.title from "在这里输入结构的名称" to a
meaningful label like "本地卷缓存池状态", and populate each field.desc for the
VmLocalVolumeCachePoolStatus enum values (Connecting, Connected, Disconnected)
with concise descriptions (e.g., "缓存池正在连接中", "缓存池已连接", "缓存池已断开连接") so the doc
block for doc { ... } clearly documents the meaning of Connecting, Connected and
Disconnected.
🤖 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/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy`:
- Around line 6-10: The API documentation metadata in
APIGetBlockDevicesMsgDoc_zh_cn.groovy is still using placeholders: update the
category value (replace "未知类别" with the appropriate category such as "存储" or the
product-specific category), replace the desc string (currently "在这里填写API描述")
with a concise Chinese description of what GetBlockDevices does, and fill any
empty request descriptions (the request block) with parameter explanations and
examples; ensure you edit the title/category/desc entries in
APIGetBlockDevicesMsgDoc_zh_cn.groovy (and apply the same fixes to the other
identical stub mentioned) so generated docs have proper classification and usage
details.

In
`@header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy`:
- Around line 5-80: 将文档模板中的 title 从占位文本替换为结构真实名称(HostBlockDeviceStruct
或项目中对应名称),并为所有 field 的 desc 补充简短明确描述,优先完善
wwid、hctl、source、transport、targetIdentifier 的含义和示例(例如 wwid 为设备全局唯一标识,hctl
为主控:总线:设备:功能,source 为设备来源/设备节点,transport 为传输协议如 sata/scsi/usb,targetIdentifier
为目标标识或 iSCSI target 名称),同时为其他字段(vendor、model、serial、path、size
等)补上类型一致的易懂描述和单位提示;在修改时直接编辑 title 字段和每个 field 块中的 desc 条目以反映这些说明。

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy`:
- Line 9: The document title in VmLocalVolumeCacheInventoryDoc_zh_cn.groovy is
still the placeholder "在这里输入结构的名称"; update the title field to a meaningful
Chinese name (for example "虚拟机本地卷缓存清单" or "VmLocalVolumeCacheInventory") by
replacing the current title string in the file so the generated documentation
has the correct header.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy`:
- Around line 7-11: Replace the placeholder title and empty WriteBack
description in VmLocalVolumeCacheModeDoc_zh_cn.groovy: set the title to the real
structure name (e.g., "本地虚拟机卷缓存模式说明" or the actual structure identifier used
elsewhere) and fill the field with a concise Chinese description for the
WriteBack mode explaining its semantics (e.g., that writes are cached locally
and flushed to backing storage asynchronously, potential data-loss/consistency
considerations, and when to use it). Update the title string and the field with
name "WriteBack" and its desc so the generated Chinese docs are meaningful and
match the intended behavior.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy`:
- Around line 5-20: Update the doc block in
VmLocalVolumeCachePoolStateDoc_zh_cn.groovy: replace the placeholder title with
the actual structure name "VmLocalVolumeCachePoolState" and fill the two field
desc values with brief Chinese descriptions explaining the meanings of Enabled
(e.g., "表示本地卷缓存池已启用,可以被 VM 使用") and Disabled (e.g., "表示本地卷缓存池已禁用,不可被 VM 使用");
modify the existing doc { ... } block and the field { name "Enabled" ... } and
field { name "Disabled" ... } entries accordingly so the generated enum
documentation is readable.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy`:
- Around line 9-26: VmLocalVolumeCacheState 文档中三个枚举字段的 desc 都为空,导致用户无法理解状态含义;请在
VmLocalVolumeCacheStateDoc_zh_cn.groovy 中为字段
"None"、"Uninstantiated"、"Instantiated" 分别补充简短明确的中文描述,说明每个状态代表的含义和使用场景(例如 "None"
表示未设置/无缓存,"Uninstantiated" 表示已注册但未实例化,"Instantiated" 表示缓存已创建并可用),确保使用与类型
VmLocalVolumeCacheState 对应的字段块中的 desc 属性进行更新。
- Line 7: The document title is still a placeholder; open
VmLocalVolumeCacheStateDoc_zh_cn.groovy and replace the placeholder title string
in the title declaration (currently "在这里输入结构的名称") with an appropriate Chinese
title such as "虚拟机本地卷缓存状态" or the English name "VmLocalVolumeCacheState" so the
`title` field reflects the real document name.

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java`:
- Around line 218-225: The APICreateVmInstanceMsg fields related to volume cache
lack API-layer validation: add resource existence check for
rootVolumeCachePoolUuid and validate/trim per-disk cache fields inside
VmInstanceApiInterceptor.validate(APICreateVmInstanceMsg). Specifically, in
validate(APICreateVmInstanceMsg) trim and validate rootVolumeCachePoolUuid
(ensure non-empty UUID format and resource exists), iterate diskAOs and
dataDiskCacheConfigOnIndex/VolumeCacheConfig entries to trim cachePoolUuid,
verify each cachePoolUuid refers to an existing CachePool resource, and validate
cacheMode values against the allowed enum (e.g., "WriteBack"), rejecting or
normalizing invalid/blank inputs. Ensure you reference
APICreateVmInstanceMsg.rootVolumeCachePoolUuid, APICreateVmInstanceMsg.diskAOs,
DiskAO.cachePoolUuid/cacheMode, and VolumeCacheConfig.cachePoolUuid/cacheMode
when implementing these checks.

In `@header/src/main/java/org/zstack/header/volume/VolumeAO.java`:
- Around line 91-94: The `@JoinColumn` on VolumeAO.localVolumeCache is reversed:
VolumeAO (extends ResourceVO) has PK uuid, while VmLocalVolumeCacheVO holds the
FK volumeUuid; update the mapping on the localVolumeCache field to reference
VolumeAO's uuid by setting the JoinColumn name to "uuid" and adding
referencedColumnName="volumeUuid" (keep insertable=false, updatable=false as
before) so Hibernate will join VmLocalVolumeCacheVO.volumeUuid to VolumeAO.uuid
(mirror the approach used in VolumeSnapshotVO).

In `@header/src/main/java/org/zstack/header/volume/VolumeInventory.java`:
- Line 220: VolumeInventory.valueOf() is causing NPE because it calls
vo.getLocalVolumeCache().toInventory() unguarded; change the logic around
inv.setLocalVolumeCache(...) to null-check vo.getLocalVolumeCache() first and
only call toInventory() when it is non-null (set inv.setLocalVolumeCache(null)
or skip the setter when local cache is absent) so that volumes without a local
cache do not trigger a NullPointerException.

---

Duplicate comments:
In `@header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java`:
- Around line 22-33: The equals/hashCode currently only consider wwid in
HostBlockDeviceStruct, causing different devices with null wwid to be treated
equal; update equals(Object) to, when wwid is non-null, compare by wwid, and
when wwid is null, fall back to comparing a stable secondary key (e.g. path and
hctl fields) and update hashCode() to return a consistent hash based on the same
branch (wwid.hashCode() when present, otherwise Objects.hash(path, hctl) or
equivalent) so both equals and hashCode use the identical decision logic.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java`:
- Around line 33-35: The metadata deserialization can throw if metadata is an
empty or whitespace-only string; update the check in
VmLocalVolumeCachePoolInventory (where vo.getMetadata() is used) to ensure you
only call JSONObjectUtil.toObject(...) when vo.getMetadata() != null &&
vo.getMetadata().trim().length() > 0 (or use
!vo.getMetadata().trim().isEmpty()), so blank strings are treated as absent and
skipped before deserializing.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java`:
- Around line 35-36: The entity VmLocalVolumeCacheVO now declares a persistent
field installPath (`@Column`(length = 2048)) but the DB upgrade script did not add
the corresponding column, causing Hibernate/JPA to fail at startup; fix by
updating the migration/upgrade script to ALTER TABLE the VmLocalVolumeCacheVO
backing table to add an installPath column that matches the entity
(varchar/character length 2048, nullable if the entity allows nulls) and include
this migration in the same upgrade sequence so the schema and the
VmLocalVolumeCacheVO.installPath mapping stay consistent.
- Around line 28-31: The Javadoc on VmLocalVolumeCacheVO incorrectly states
"VmLocalVolumeCachePoolVO in kvm plugin"; update the comment on
VmLocalVolumeCacheVO to reference the correct package/module
(org.zstack.header.localVolumeCache) and clarify the intent (e.g., "References
VmLocalVolumeCachePoolVO in org.zstack.header.localVolumeCache — no `@ForeignKey`
to avoid cross-module dependency") so the module ownership and rationale remain
accurate; ensure the symbol VmLocalVolumeCachePoolVO and the note about avoiding
`@ForeignKey` are preserved and worded correctly.

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java`:
- Around line 43-49: In CacheTO.valueOf, guard against a null cacheMode on
VmLocalVolumeCacheInventory by checking inv.getCacheMode() before calling
.name(); call to.setCacheMode(inv.getCacheMode().name()) only when
getCacheMode() != null, otherwise set a safe value (e.g. null or empty string)
to avoid NullPointerException while preserving backward compatibility; locate
the logic in CacheTO.valueOf and the interactions with
VmLocalVolumeCacheInventory.getCacheMode and CacheTO.setCacheMode to implement
this null-safe assignment.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java`:
- Around line 5193-5266: Add lightweight pre-send validation to these agent
command DTOs by introducing a validate() contract on AgentCommand (or
overrideable method) and implement it in the specific commands to assert
required fields are non-null/non-empty; e.g., in
InitPoolCmd/ConnectPoolCmd/ExtendPoolCmd/DeletePoolCmd/CheckPoolCmd/GetPoolCapacityCmd/GCPoolCmd
check poolUuid and mountPoint (and volumes for GCPoolCmd), in
AllocateCacheCmd/DeleteCacheCmd/FlushCacheCmd/GetCacheCapacityCmd ensure
poolUuid and volume are present, and in
AttachVolumeCacheCmd/DetachVolumeCacheCmd ensure instanceUuid and volume are
present; have validate() throw a clear IllegalArgumentException (or a checked
validation exception) with which-field-is-missing so callers can reject requests
before sending to the agent.

---

Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java`:
- Around line 404-434: The warning log inside VmInstantiateOtherDiskFlow's
NoRollbackFlow when enabling cache (in the run override that sends
EnableVolumeCacheMsg) lacks useful context; update the logger.warn call to
include the pool UUID and cache mode (use diskAO.getCachePoolUuid() and
diskAO.getCacheMode()) along with the volume UUID and reply.getError(), so the
message from the EnableVolumeCacheMsg callback contains
volumeInventory.getUuid(), diskAO.getCachePoolUuid(), diskAO.getCacheMode(), and
reply.getError() while preserving the current non-failing behavior.
- Line 35: Remove the unused import java.sql.Array from the top of
VmInstantiateOtherDiskFlow (the import statement referencing java.sql.Array) —
locate the import in VmInstantiateOtherDiskFlow and delete that line so there
are no unused imports remaining.

In
`@header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy`:
- Around line 6-17: Replace the placeholder title and incorrect description in
the doc block: update the doc.title (currently "在这里输入结构的名称") to a meaningful
title like "获取块设备响应" and change the ref for blockDevices (ref.name
"blockDevices", ref.path
"org.zstack.header.host.APIGetBlockDevicesEvent.blockDevices") so its desc is a
proper description (e.g., "主机上的块设备列表") or an empty string "" instead of the
literal "null"; make these edits inside the doc { ... } block in
APIGetBlockDevicesEventDoc_zh_cn.groovy where clz HostBlockDeviceStruct.class is
referenced.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy`:
- Around line 5-27: Replace the placeholder title and fill missing enum
descriptions in the Groovy doc: update the doc.title from "在这里输入结构的名称" to a
meaningful label like "本地卷缓存池状态", and populate each field.desc for the
VmLocalVolumeCachePoolStatus enum values (Connecting, Connected, Disconnected)
with concise descriptions (e.g., "缓存池正在连接中", "缓存池已连接", "缓存池已断开连接") so the doc
block for doc { ... } clearly documents the meaning of Connecting, Connected and
Disconnected.

In
`@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsgDoc_zh_cn.groovy`:
- Around line 332-378: The new cache-related API parameters
(enableRootVolumeCache, cacheMode, rootVolumeCachePoolUuid, rootVolumeCacheMode,
dataDiskCacheConfigOnIndex) have empty desc fields; update the column blocks in
APICreateVmInstanceMsgDoc_zh_cn.groovy to provide concise Chinese descriptions
for each parameter explaining purpose and accepted values (e.g.,
enableRootVolumeCache: whether to enable root volume caching;
cacheMode/rootVolumeCacheMode: cache strategy such as "WriteBack";
rootVolumeCachePoolUuid: UUID of the cache pool to use;
dataDiskCacheConfigOnIndex: map of per-disk cache index configurations), keeping
the existing enclosedIn, location, type, optional and since fields unchanged and
matching wording style used by neighboring parameter docs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 247b1548-db44-4173-8cdd-dff84300b663

📥 Commits

Reviewing files that changed from the base of the PR and between e037533 and 49191cf.

⛔ Files ignored due to path filters (43)
  • conf/globalConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCache.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/springConfigXml/VmInstanceManager.xml is excluded by !**/*.xml
  • conf/springConfigXml/localVolumeCache.xml is excluded by !**/*.xml
  • conf/zstack.xml is excluded by !**/*.xml
  • sdk/src/main/java/SourceClassMap.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CacheTO.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeTO.java is excluded by !sdk/**
📒 Files selected for processing (52)
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/HostInventory.java
  • header/src/main/java/org/zstack/header/host/HostVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheMsg.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheReply.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • header/src/main/java/org/zstack/header/volume/VolumeAO.java
  • header/src/main/java/org/zstack/header/volume/VolumeInventory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
💤 Files with no reviewable changes (2)
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
✅ Files skipped from review due to trivial changes (1)
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy
🚧 Files skipped from review as they are similar to previous changes (17)
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheReply.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • header/src/main/java/org/zstack/header/localVolumeCache/EnableVolumeCacheMsg.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java

Comment on lines +6 to +10
title "GetBlockDevices"

category "未知类别"

desc """在这里填写API描述"""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

API 文档元信息还没填完。

这里的 category 仍是“未知类别”,接口描述也还是模板文案,request 描述还是空字符串。这样生成出来的 API 文档会缺少分类和用途说明,建议在合入前补齐。

Also applies to: 20-20

🤖 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/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy`
around lines 6 - 10, The API documentation metadata in
APIGetBlockDevicesMsgDoc_zh_cn.groovy is still using placeholders: update the
category value (replace "未知类别" with the appropriate category such as "存储" or the
product-specific category), replace the desc string (currently "在这里填写API描述")
with a concise Chinese description of what GetBlockDevices does, and fill any
empty request descriptions (the request block) with parameter explanations and
examples; ensure you edit the title/category/desc entries in
APIGetBlockDevicesMsgDoc_zh_cn.groovy (and apply the same fixes to the other
identical stub mentioned) so generated docs have proper classification and usage
details.

Comment on lines +5 to +80
doc {

title "在这里输入结构的名称"

field {
name "wwid"
desc ""
type "String"
since "5.5.6"
}
field {
name "vendor"
desc ""
type "String"
since "5.5.6"
}
field {
name "model"
desc ""
type "String"
since "5.5.6"
}
field {
name "wwn"
desc ""
type "String"
since "5.5.6"
}
field {
name "serial"
desc ""
type "String"
since "5.5.6"
}
field {
name "hctl"
desc ""
type "String"
since "5.5.6"
}
field {
name "type"
desc ""
type "String"
since "5.5.6"
}
field {
name "path"
desc ""
type "String"
since "5.5.6"
}
field {
name "size"
desc ""
type "Long"
since "5.5.6"
}
field {
name "source"
desc ""
type "String"
since "5.5.6"
}
field {
name "transport"
desc ""
type "String"
since "5.5.6"
}
field {
name "targetIdentifier"
desc ""
type "String"
since "5.5.6"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

结构体文档缺少有效说明。

现在除了字段名和类型之外几乎没有可读信息:title 还是模板文案,所有字段 desc 都为空。建议至少补上结构名,以及 wwidhctlsourcetransporttargetIdentifier 这些不够直观字段的说明。

🤖 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/host/HostBlockDeviceStructDoc_zh_cn.groovy`
around lines 5 - 80, 将文档模板中的 title 从占位文本替换为结构真实名称(HostBlockDeviceStruct
或项目中对应名称),并为所有 field 的 desc 补充简短明确描述,优先完善
wwid、hctl、source、transport、targetIdentifier 的含义和示例(例如 wwid 为设备全局唯一标识,hctl
为主控:总线:设备:功能,source 为设备来源/设备节点,transport 为传输协议如 sata/scsi/usb,targetIdentifier
为目标标识或 iSCSI target 名称),同时为其他字段(vendor、model、serial、path、size
等)补上类型一致的易懂描述和单位提示;在修改时直接编辑 title 字段和每个 field 块中的 desc 条目以反映这些说明。


doc {

title "在这里输入结构的名称"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

文档标题未填写,仍为占位符文本。

title 字段仍为模板占位符 "在这里输入结构的名称",应替换为实际的文档标题,如 "虚拟机本地卷缓存清单""VmLocalVolumeCacheInventory"

🤖 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/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy`
at line 9, The document title in VmLocalVolumeCacheInventoryDoc_zh_cn.groovy is
still the placeholder "在这里输入结构的名称"; update the title field to a meaningful
Chinese name (for example "虚拟机本地卷缓存清单" or "VmLocalVolumeCacheInventory") by
replacing the current title string in the file so the generated documentation
has the correct header.

Comment on lines +7 to +11
title "在这里输入结构的名称"

field {
name "WriteBack"
desc ""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

请补全真实的文档标题和 WriteBack 说明。

Line 7 还是占位文本,Line 11 的 desc 为空,最终生成的中文文档会缺少可读信息。这里至少应填入实际结构名,并补充 WriteBack 模式的语义说明。

🤖 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/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy`
around lines 7 - 11, Replace the placeholder title and empty WriteBack
description in VmLocalVolumeCacheModeDoc_zh_cn.groovy: set the title to the real
structure name (e.g., "本地虚拟机卷缓存模式说明" or the actual structure identifier used
elsewhere) and fill the field with a concise Chinese description for the
WriteBack mode explaining its semantics (e.g., that writes are cached locally
and flushed to backing storage asynchronously, potential data-loss/consistency
considerations, and when to use it). Update the title string and the field with
name "WriteBack" and its desc so the generated Chinese docs are meaningful and
match the intended behavior.

Comment on lines +5 to +20
doc {

title "在这里输入结构的名称"

field {
name "Enabled"
desc ""
type "VmLocalVolumeCachePoolState"
since "5.5.6"
}
field {
name "Disabled"
desc ""
type "VmLocalVolumeCachePoolState"
since "5.5.6"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

请把结构文档里的占位内容补全。

title 还是模板文案,两个状态项的 desc 也为空;如果直接提交,生成出来的枚举文档几乎不可用。至少把标题改成实际结构名,并补一句 Enabled / Disabled 的语义说明。

🤖 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/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy`
around lines 5 - 20, Update the doc block in
VmLocalVolumeCachePoolStateDoc_zh_cn.groovy: replace the placeholder title with
the actual structure name "VmLocalVolumeCachePoolState" and fill the two field
desc values with brief Chinese descriptions explaining the meanings of Enabled
(e.g., "表示本地卷缓存池已启用,可以被 VM 使用") and Disabled (e.g., "表示本地卷缓存池已禁用,不可被 VM 使用");
modify the existing doc { ... } block and the field { name "Enabled" ... } and
field { name "Disabled" ... } entries accordingly so the generated enum
documentation is readable.


doc {

title "在这里输入结构的名称"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

文档标题未填写,仍为占位符文本。

title 字段仍为模板占位符 "在这里输入结构的名称",应替换为实际的文档标题,如 "虚拟机本地卷缓存状态""VmLocalVolumeCacheState"

🤖 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/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy`
at line 7, The document title is still a placeholder; open
VmLocalVolumeCacheStateDoc_zh_cn.groovy and replace the placeholder title string
in the title declaration (currently "在这里输入结构的名称") with an appropriate Chinese
title such as "虚拟机本地卷缓存状态" or the English name "VmLocalVolumeCacheState" so the
`title` field reflects the real document name.

Comment on lines +9 to +26
field {
name "None"
desc ""
type "VmLocalVolumeCacheState"
since "5.5.6"
}
field {
name "Uninstantiated"
desc ""
type "VmLocalVolumeCacheState"
since "5.5.6"
}
field {
name "Instantiated"
desc ""
type "VmLocalVolumeCacheState"
since "5.5.6"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

所有字段的 desc 描述均为空。

NoneUninstantiatedInstantiated 三个状态的描述字段均未填写。建议补充各状态的含义说明,以便用户理解各状态的用途。

🤖 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/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy`
around lines 9 - 26, VmLocalVolumeCacheState 文档中三个枚举字段的 desc 都为空,导致用户无法理解状态含义;请在
VmLocalVolumeCacheStateDoc_zh_cn.groovy 中为字段
"None"、"Uninstantiated"、"Instantiated" 分别补充简短明确的中文描述,说明每个状态代表的含义和使用场景(例如 "None"
表示未设置/无缓存,"Uninstantiated" 表示已注册但未实例化,"Instantiated" 表示缓存已创建并可用),确保使用与类型
VmLocalVolumeCacheState 对应的字段块中的 desc 属性进行更新。

Comment on lines +218 to +225
@APIParam(required = false)
private String rootVolumeCachePoolUuid;

@APIParam(required = false, validValues = {"WriteBack"})
private String rootVolumeCacheMode;

@APIParam(required = false)
private Map<Integer, VolumeCacheConfig> dataDiskCacheConfigOnIndex;
Copy link

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
# 查看 APIMessage 的参数收集/校验范围
fd 'APIMessage.java' -t f --exec sed -n '1,260p' {} \;

printf '\n---- APICreateVmInstanceMsg cache-related fields ----\n'
fd 'APICreateVmInstanceMsg.java' -t f --exec sed -n '208,390p' {} \;

printf '\n---- cachePoolUuid / cacheMode usages in request DTOs ----\n'
rg -n "rootVolumeCachePoolUuid|cachePoolUuid|cacheMode|dataDiskCacheConfigOnIndex" --type java

Repository: MatheMatrix/zstack

Length of output: 28821


🏁 Script executed:

#!/bin/bash
# Find VmInstanceApiInterceptor and check cache validation
fd 'VmInstanceApiInterceptor.java' -t f --exec wc -l {} \;
echo "---"
fd 'VmInstanceApiInterceptor.java' -t f --exec head -n 50 {} \;
echo "--- Searching for validate methods and cache-related code ---"
fd 'VmInstanceApiInterceptor.java' -t f --exec rg -n "validate|cacheMode|cachePoolUuid" {} \;

Repository: MatheMatrix/zstack

Length of output: 9330


🏁 Script executed:

#!/bin/bash
# Get the validate method for APICreateVmInstanceMsg
fd 'VmInstanceApiInterceptor.java' -t f --exec sed -n '1193,1350p' {} \;

Repository: MatheMatrix/zstack

Length of output: 7571


🏁 Script executed:

#!/bin/bash
# Search for cache-related validation in the entire VmInstanceApiInterceptor
rg -n "cacheMode|cachePoolUuid|rootVolumeCachePoolUuid|dataDiskCacheConfigOnIndex" compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if nested DTOs in APIMessage are handled for trim/validation
# Look at how APIMessage.validate() processes nested objects
fd 'APIMessage.java' -t f --exec sed -n '193,215p' {} \;

echo "---"
# Check if there are any validators for the cache fields in DiskAO or VolumeCacheConfig
rg -n "DiskAO|VolumeCacheConfig" header/src/main/java/org/zstack/header/vm/ -A 5 -B 2 | head -100

Repository: MatheMatrix/zstack

Length of output: 10994


🏁 Script executed:

#!/bin/bash
# Check if there's any explicit validation for cache fields elsewhere
# Search for validateCacheMode, validateCachePoolUuid, etc.
rg -n "cache|Cache" compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java --type java

echo "---"
# Check APICreateVmInstanceMsg for any validate() override
fd 'APICreateVmInstanceMsg.java' -t f --exec rg -n "public void validate" {} \;

Repository: MatheMatrix/zstack

Length of output: 65


🏁 Script executed:

#!/bin/bash
# Check the full APICreateVmInstanceMsg file for validate() method
fd 'APICreateVmInstanceMsg.java' -t f | head -1 | xargs wc -l
echo "---"
fd 'APICreateVmInstanceMsg.java' -t f --exec rg -n "validate\(\)" {} \;

Repository: MatheMatrix/zstack

Length of output: 139


🏁 Script executed:

#!/bin/bash
# Verify whether the top-level cache fields are actually trimmed/validated by APIMessage framework
# Check if `@APIParam` fields are processed in APIMessage.validate()

echo "=== Checking APICreateVmInstanceMsg cache field annotations ==="
fd 'APICreateVmInstanceMsg.java' -t f --exec sed -n '214,226p' {} \;

echo ""
echo "=== Verifying APIMessage.validate() processes `@APIParam` fields ==="
fd 'APIMessage.java' -t f --exec sed -n '200,230p' {} \;

echo ""
echo "=== Checking if DiskAO/VolumeCacheConfig nested DTO fields have `@APIParam` ==="
fd 'APICreateVmInstanceMsg.java' -t f --exec sed -n '235,250p' {} \;
fd 'APICreateVmInstanceMsg.java' -t f --exec sed -n '360,385p' {} \;

echo ""
echo "=== Checking if nested fields in DiskAO are trimmed ==="
fd 'APICreateVmInstanceMsg.java' -t f --exec grep -n "@APIParam" {} \; | grep -i "cache"

Repository: MatheMatrix/zstack

Length of output: 3091


嵌套缓存配置字段缺乏 API 层校验。

顶层的 rootVolumeCachePoolUuid 缺少 resourceType 约束,而 DiskAOVolumeCacheConfig 中的 cachePoolUuidcacheMode 由于没有 @APIParam 注解,完全绕过框架的 trim 和校验流程。用户粘贴的包含空格、换行符的 UUID 或非法 mode 值会直接进入系统,问题延后到调度/KVM agent 阶段才暴露。建议在 VmInstanceApiInterceptor 的 validate(APICreateVmInstanceMsg) 方法中补充:

  1. rootVolumeCachePoolUuid 添加资源存在性校验
  2. 遍历 diskAOscachePoolUuid/cacheMode,逐个 trim 并校验 UUID 存在性和 mode 枚举值
🤖 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/APICreateVmInstanceMsg.java` around
lines 218 - 225, The APICreateVmInstanceMsg fields related to volume cache lack
API-layer validation: add resource existence check for rootVolumeCachePoolUuid
and validate/trim per-disk cache fields inside
VmInstanceApiInterceptor.validate(APICreateVmInstanceMsg). Specifically, in
validate(APICreateVmInstanceMsg) trim and validate rootVolumeCachePoolUuid
(ensure non-empty UUID format and resource exists), iterate diskAOs and
dataDiskCacheConfigOnIndex/VolumeCacheConfig entries to trim cachePoolUuid,
verify each cachePoolUuid refers to an existing CachePool resource, and validate
cacheMode values against the allowed enum (e.g., "WriteBack"), rejecting or
normalizing invalid/blank inputs. Ensure you reference
APICreateVmInstanceMsg.rootVolumeCachePoolUuid, APICreateVmInstanceMsg.diskAOs,
DiskAO.cachePoolUuid/cacheMode, and VolumeCacheConfig.cachePoolUuid/cacheMode
when implementing these checks.

Related: ZSTAC-81715

Change-Id: I6d747a786b73746a6f756a6c72787479726e6765
@MatheMatrix MatheMatrix force-pushed the sync/haidong.pang/feature/ZSTAC-81715 branch from 49191cf to 5c2d0ec Compare March 12, 2026 16:26
Copy link

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java (1)

68-90: ⚠️ Potential issue | 🟡 Minor

复制构造函数这里需要深拷贝 cache

CacheTO 是可变对象,Line 89 直接复用引用后,副本上的任何 setCache*() 调用都会回写原始 VolumeTO,这和复制构造函数的语义不一致。建议在这里逐字段拷贝,或给 CacheTO 增加复制构造函数。

建议修改
-        this.cache = other.cache;
+        if (other.cache != null) {
+            CacheTO copiedCache = new CacheTO();
+            copiedCache.setCacheUuid(other.cache.getCacheUuid());
+            copiedCache.setPoolUuid(other.cache.getPoolUuid());
+            copiedCache.setInstallPath(other.cache.getInstallPath());
+            copiedCache.setCacheMode(other.cache.getCacheMode());
+            this.cache = copiedCache;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java` around lines 68 - 90,
The copy constructor VolumeTO(VolumeTO other) currently assigns this.cache =
other.cache which shares the mutable CacheTO instance; change it to perform a
deep copy of cache (either by calling a CacheTO copy constructor like new
CacheTO(other.cache) or by constructing a new CacheTO and copying each field) so
that subsequent setCache*() calls on the copy do not mutate the original; if
CacheTO lacks a copy constructor, add one (or implement clone()/copyFrom()) and
use it from VolumeTO's constructor.
♻️ Duplicate comments (9)
plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java (1)

43-48: ⚠️ Potential issue | 🟠 Major

cacheMode 增加空值保护

这里直接调用 inv.getCacheMode().name(),只要历史数据或异常数据里 cacheMode 为空,就会在组装 VolumeTO.cache 时直接 NPE。建议至少在取 name() 前判空,保持非空场景的现有行为。

建议修改
     public static CacheTO valueOf(VmLocalVolumeCacheInventory inv) {
+        if (inv == null) {
+            return null;
+        }
         CacheTO to = new CacheTO();
         to.setCacheUuid(inv.getUuid());
         to.setPoolUuid(inv.getPoolUuid());
         to.setInstallPath(inv.getInstallPath());
-        to.setCacheMode(inv.getCacheMode().name());
+        to.setCacheMode(inv.getCacheMode() == null ? null : inv.getCacheMode().name());
         return to;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java` around lines 43 - 48,
In CacheTO.valueOf, avoid calling inv.getCacheMode().name() directly to prevent
NPE when cacheMode is null; check inv.getCacheMode() for null first and only
call name() when non-null, then pass the resulting String (or null) to
to.setCacheMode so existing non-null behavior is preserved; update the logic
around VmLocalVolumeCacheInventory.getCacheMode() and the to.setCacheMode(...)
call accordingly.
plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java (1)

5194-5267: ⚠️ Potential issue | 🟡 Minor

这批新增命令对象缺少必填字段校验

poolUuidmountPointinstanceUuidvolume 都是请求边界上的关键字段,但这里全部直接暴露为 public,没有任何非空/非空白检查。上游一旦漏填,失败会延后到 agent 侧才暴露,定位会很被动。建议在发送前加统一校验,或给必填字段补上注解/显式检查。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines
5194 - 5267, Several new AgentCommand subclasses (InitPoolCmd, ConnectPoolCmd,
ExtendPoolCmd, DeletePoolCmd, CheckPoolCmd, GetPoolCapacityCmd, GCPoolCmd,
AllocateCacheCmd, DeleteCacheCmd, FlushCacheCmd, GetCacheCapacityCmd,
AttachVolumeCacheCmd, DetachVolumeCacheCmd) expose critical request fields
(poolUuid, mountPoint, instanceUuid, volume) without validation; add explicit
non-null/non-blank checks by implementing a validate() method on these command
classes (or a single protected validateRequired(String name, Object val) helper
on AgentCommand) that throws IllegalArgumentException when
poolUuid/mountPoint/instanceUuid are null/empty or when volume is null, override
where needed (e.g., GCPoolCmd checks volumes list), and ensure callers invoke
cmd.validate() before sending the command to the agent.
compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java (1)

149-155: ⚠️ Potential issue | 🟠 Major

根卷缓存大小仍然没有对齐 ISO 分支。

Line 150-154 这里只要 image 有值就优先累计 image.getSize(),但本文件 prepareMsg() 的 Line 72-78 已经把 ISO 场景的根盘大小定义为 rootDiskOffering。两边不一致会把 volumeCache::requiredSize 算大/算错,直接影响主机筛选。

🛠 建议修改
-            ImageInventory image = spec.getImageSpec().getInventory();
-            if (image != null && image.getSize() != 0) {
+            ImageInventory image = spec.getImageSpec().getInventory();
+            boolean nonIsoImage = image != null
+                    && !ImageMediaType.ISO.toString().equals(image.getMediaType());
+            if (nonIsoImage && image.getSize() > 0) {
                 totalCacheSize += image.getSize();
             } else if (spec.getRootDiskOffering() != null) {
                 totalCacheSize += spec.getRootDiskOffering().getDiskSize();
             }
🤖 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/VmAllocateHostFlow.java` around
lines 149 - 155, The code in VmAllocateHostFlow that accumulates totalCacheSize
(when enableRootVolumeCache is true) currently always prefers image.getSize(),
which contradicts prepareMsg() where ISO-root volumes are represented via
spec.getRootDiskOffering(); modify the accumulation logic in VmAllocateHostFlow
so that if the image is an ISO scenario (or image size is zero) you use
spec.getRootDiskOffering().getDiskSize() instead of image.getSize() — i.e.,
check the image/ISO condition and prefer rootDiskOffering for ISO cases to keep
totalCacheSize / volumeCache::requiredSize consistent with prepareMsg().
header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy (1)

7-79: ⚠️ Potential issue | 🟡 Minor

结构体文档还是占位内容。

Line 7 仍是模板 title,Line 11-77 的 desc 也全部为空。现在生成出来的文档几乎不可用,至少需要把结构名和关键字段说明补齐。

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

In
`@header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy`
around lines 7 - 79, 当前文档仍然是占位模板:将 title 从模板文本替换为正确结构名(例如 "HostBlockDeviceStruct
结构体" 或项目中实际的结构名),并为每个 field(wwid, vendor, model, wwn, serial, hctl, type, path,
size, source, transport, targetIdentifier)补充简短的中文说明(描述字段含义、单位或示例值),保持 type 与
since 不变,确保所有 desc 字段非空并能帮助读者理解每个字段的用途。
header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java (1)

23-35: ⚠️ Potential issue | 🟠 Major

避免把所有 wwid == null 的设备都判成同一个对象。

当前 equals()/hashCode()wwid 为空时会把不同设备都折叠成“相等”,后续只要进入 Set、去重列表或作为 Map key,就会误丢设备条目。这里至少需要回退到稳定的次级键,例如 path + hctl

建议修改
+import java.util.Objects;
+
     `@Override`
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
         HostBlockDeviceStruct that = (HostBlockDeviceStruct) o;
-        return wwid != null ? wwid.equals(that.wwid) : that.wwid == null;
+        if (wwid != null && that.wwid != null) {
+            return wwid.equals(that.wwid);
+        }
+        return Objects.equals(path, that.path) && Objects.equals(hctl, that.hctl);
     }
 
     `@Override`
     public int hashCode() {
-        return wwid != null ? wwid.hashCode() : 0;
+        return wwid != null ? wwid.hashCode() : Objects.hash(path, hctl);
     }
🤖 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/host/HostBlockDeviceStruct.java`
around lines 23 - 35, The current HostBlockDeviceStruct.equals() and hashCode()
treat all instances with wwid==null as equal; update both methods so equality
is: if wwid != null use wwid.equals(other.wwid), otherwise fall back to
comparing the stable secondary key composed of path and hctl (e.g.,
Objects.equals(path, other.path) && Objects.equals(hctl, other.hctl)), and make
hashCode return a combined hash of (wwid != null ? wwid : combine(path,hctl)) so
null WWIDs no longer collide; modify the equals(), hashCode() implementations in
HostBlockDeviceStruct accordingly and use null-safe utilities
(Objects.equals/Objects.hash).
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy (1)

9-49: ⚠️ Potential issue | 🟡 Minor

文档里仍有占位值和空描述。

title 还是占位文本,poolUuid/installPath 的说明为空,cacheMode/state 的说明还会把字符串 "null" 直接渲染到文档里。这个文件当前生成出来的文档质量不可用,建议把这些占位内容一次性补齐。As per coding guidelines, “代码应尽量做到自解释,对少于两行的说明可以直接写在代码中。对于较长的注释,需要仔细校对并随代码更新,确保内容正确。”

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

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy`
around lines 9 - 49, Replace the placeholder title and fill missing
descriptions: update the title string to a meaningful Chinese name for the
structure, add concise desc texts for the fields poolUuid and installPath, and
replace the literal "null" descs in the ref blocks for cacheMode and state with
short explanatory phrases (or remove the desc property if no extra description
is needed); make these descriptions self-contained and under two lines where
possible and edit the blocks referencing VmLocalVolumeCacheInventory.cacheMode
and VmLocalVolumeCacheInventory.state, plus the field entries named poolUuid and
installPath, so the generated docs are complete and no placeholders remain.
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java (1)

28-31: ⚠️ Potential issue | 🟡 Minor

这里的模块归属说明还是不准确。

VmLocalVolumeCachePoolVO 就在当前 header/localVolumeCache 这一侧,注释里继续写 “in kvm plugin” 会误导后续维护者判断依赖边界。建议删掉这段模块归属,或改成实际位置。

As per coding guidelines: “对于较长的注释,需要仔细校对并随代码更新,确保内容正确。”

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

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java`
around lines 28 - 31, 注释中关于 VmLocalVolumeCachePoolVO 的模块归属描述不准确;打开
VmLocalVolumeCacheVO 类并编辑该注释(the javadoc block above the field referencing
VmLocalVolumeCachePoolVO)中移除或修正 “in kvm plugin” 的表述,改为要么仅保留“References
VmLocalVolumeCachePoolVO”要么替换为正确的位置说明(例如 “located in
header/localVolumeCache”),确保注释只引用 VmLocalVolumeCachePoolVO 并且不引入错误的模块归属信息。
header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java (1)

218-219: ⚠️ Potential issue | 🟠 Major

为缓存相关入参补齐 API 层校验。

rootVolumeCachePoolUuid 这里只有 @APIParam(required = false),不会自动做资源存在性校验;DiskAO / VolumeCacheConfig 里的 cachePoolUuidcacheMode 也没有任何 @APIParamvalidate(...) 兜底。现在这些值会被原样传进后续的 CreateVmInstanceMsg / VmInstanceSpec,带空格的 UUID 或非法 mode 只能在更靠后的调度/agent 阶段才暴露。建议至少给 rootVolumeCachePoolUuid 增加 resourceType,并在 VmInstanceApiInterceptor.validate(APICreateVmInstanceMsg) 中统一 trim 和校验嵌套缓存配置。

As per coding guidelines: “注意检查来自 Message 的参数是否做过 trim,用户可能在浏览器上复制粘贴的数据带有空格、换行符等”

Also applies to: 224-225, 241-243, 366-384

🤖 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/APICreateVmInstanceMsg.java` around
lines 218 - 219, The API currently lacks validation and trimming for
cache-related fields (APICreateVmInstanceMsg.rootVolumeCachePoolUuid and nested
DiskAO/VolumeCacheConfig.cachePoolUuid/cacheMode), so add resourceType to the
`@APIParam` on rootVolumeCachePoolUuid and any similar API params to enable
resource existence checks, then implement trimming and validation in
VmInstanceApiInterceptor.validate(APICreateVmInstanceMsg) to .trim() all
incoming UUID/string fields and verify cachePoolUuid exists and cacheMode is
within allowed values before constructing CreateVmInstanceMsg/VmInstanceSpec;
reference DiskAO and VolumeCacheConfig field names when validating nested
structures so malformed/whitespace values are rejected early.
header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java (1)

50-52: ⚠️ Potential issue | 🟡 Minor

空白 metadata 会在这里直接触发反序列化异常。

这里只判断了 null,但如果库里存的是 "" 或纯空白串,JSONObjectUtil.toObject() 会在 inventory 转换阶段直接抛错。这里需要先做 blank 判断,再调用反序列化。

🤖 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/localVolumeCache/VmLocalVolumeCachePoolInventory.java`
around lines 50 - 52, The code in VmLocalVolumeCachePoolInventory checks only
for null before deserializing vo.getMetadata(), which throws when metadata is an
empty or whitespace string; update the logic around vo.getMetadata() so you
first check for blank/empty (e.g., StringUtils.isBlank(vo.getMetadata()) or
vo.getMetadata() == null || vo.getMetadata().trim().isEmpty()) and only call
JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class) when the metadata
is non-blank, otherwise leave inv.setMetadata(null) or skip setting it; keep
references to vo.getMetadata(), inv.setMetadata(...), and
JSONObjectUtil.toObject(...) when making the change.
🧹 Nitpick comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java (1)

717-738: 给失败分支补一个专用错误码。

Line 725 这里用了通用 operr(...),而本类其它 KVM handler 基本都会带 ORG_ZSTACK_KVM_xxxxx 错误码。现在这条新链路出错后不利于检索、告警和排障,建议沿用现有错误码模式补一个专用编号。

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

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java` around lines 717 - 738,
The handler for GetBlockDevicesOnHostMsg uses a generic operr(...) when
rsp.isSuccess() is false; add a dedicated KVM error code (following the
ORG_ZSTACK_KVM_xxxxx pattern used by other handlers) and return that specific
ErrorCode instead of the generic operr. Define a constant (e.g.,
ORG_ZSTACK_KVM_GET_BLOCK_DEVICES_ERROR) in the same error-constants class used
by other KVM handlers, construct an ErrorCode with that constant and the
rsp.getError() message in handle(GetBlockDevicesOnHostMsg msg) where
reply.setError(operr(...)) is currently used, and replace the operr call with
reply.setError(...) using the new ErrorCode.
🤖 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/VmAllocateHostFlow.java`:
- Around line 157-161: The code in VmAllocateHostFlow sums data disk sizes using
spec.getDataDiskOfferings().get(index).getDiskSize() while the allocation logic
in VmAllocateVolumeFlow uses spec.getDataVolumeSpecs() as the index basis; this
mismatch can produce wrong requiredSize or IndexOutOfBoundsException. Fix by
reading sizes from spec.getDataVolumeSpecs().get(index).getDiskSize() (or
otherwise map the same index space used by dataVolumeSpecs) when iterating
spec.getDataDiskCacheConfigOnIndex(), and add defensive checks that the
referenced dataVolumeSpecs entry exists and is non-null before accessing
getDiskSize() (fall back or skip if missing) so both flows use the same index
basis and avoid OOB errors.

In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateVolumeFlow.java`:
- Around line 76-88: In VmAllocateVolumeFlow, the call
spec.getDataDiskCacheConfigOnIndex().isEmpty() can NPE if the getter returns
null; change the guard to use a null-safe check (e.g.
!CollectionUtils.isEmpty(spec.getDataDiskCacheConfigOnIndex()) &&
!CollectionUtils.isEmpty(spec.getDataDiskOfferings())) before iterating, and
ensure the IntStream index loop only runs up to dataVolumeSpecs.size() and the
cache config list size (safeguard by checking both list sizes or using
min(dataVolumeSpecs.size(), spec.getDataDiskCacheConfigOnIndex().size())); keep
the existing null check for each config inside the loop.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy`:
- Around line 10-76: The document template currently contains placeholders: the
top title string and many field/ref desc values are empty or "null" (see title,
field descs for uuid, hostUuid, name, description, metadata, totalCapacity,
availableCapacity, and ref descs for state, status, caches in
VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy). Replace the placeholder title
with a real Chinese title for the VmLocalVolumeCachePoolInventory structure and
provide meaningful Chinese descriptions for each field and ref (e.g., uuid →
"资源的 UUID,唯一标识", hostUuid → "所属物理机的 UUID", metadata → "附加元数据键值对",
totalCapacity/availableCapacity → "总容量/可用容量,单位字节", refs state/status/caches →
short descriptions of enum meanings and cache set); ensure no desc remains empty
or "null" so generated API docs are complete.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java`:
- Around line 5319-5350: The toCachePoolMetadata() implementation omits copying
the source object's mountPoint into the returned CachePoolMetadata, causing the
PoolRsp.mountPoint to be lost; update toCachePoolMetadata() to read the
mountPoint field from the current object (e.g., mountPoint) and call
CachePoolMetadata.setMountPoint(...) on the created metadata before returning so
the mount point is preserved when serializing/persisting the CachePoolMetadata.

In `@storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java`:
- Around line 610-624: The current flow in VolumeManagerImpl creates a Volume
and system tags then separately persists VmLocalVolumeCacheVO (when
VolumeSystemTags.VOLUME_CACHE_ENABLE matches), which can leave volume+tags
committed while persist(cacheVO) fails and causes inconsistency; modify the code
so that creation of the volume, the related system tags, and persisting
VmLocalVolumeCacheVO happen inside the same DB transaction (use the existing
database transaction helper / `@Transactional` block or
dbf.beginTransaction()/commit()) so either all succeed or all roll back, or if
you cannot transactionally group them, add a compensating cleanup on the
persist(cacheVO) failure path that removes the earlier-created volume and its
system tags to keep data consistent; key symbols to change: VolumeManagerImpl,
VmLocalVolumeCacheVO, VolumeSystemTags.VOLUME_CACHE_ENABLE, and the
persist(cacheVO) call.

In `@storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java`:
- Around line 56-62: The new patterned tags VOLUME_CACHE_ENABLE,
VOLUME_CACHE_POOL_UUID (uses CACHE_POOL_UUID_TOKEN) and VOLUME_CACHE_MODE (uses
CACHE_MODE_TOKEN) are host/pool binding settings and must not be inherited by
snapshot-created volumes; update the copy logic in VolumeManagerImpl (the code
path that copies system tags when creating a volume from snapshot) to filter out
any system tags whose token/key prefix matches "volumeCache::" (or explicitly
exclude VOLUME_CACHE_ENABLE, VOLUME_CACHE_POOL_UUID and VOLUME_CACHE_MODE) so
the snapshot->volume copy does not propagate these cache-specific tags, or
alternatively mark them as non-inheritable and ensure the copy routine respects
that flag.

---

Outside diff comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java`:
- Around line 68-90: The copy constructor VolumeTO(VolumeTO other) currently
assigns this.cache = other.cache which shares the mutable CacheTO instance;
change it to perform a deep copy of cache (either by calling a CacheTO copy
constructor like new CacheTO(other.cache) or by constructing a new CacheTO and
copying each field) so that subsequent setCache*() calls on the copy do not
mutate the original; if CacheTO lacks a copy constructor, add one (or implement
clone()/copyFrom()) and use it from VolumeTO's constructor.

---

Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java`:
- Around line 149-155: The code in VmAllocateHostFlow that accumulates
totalCacheSize (when enableRootVolumeCache is true) currently always prefers
image.getSize(), which contradicts prepareMsg() where ISO-root volumes are
represented via spec.getRootDiskOffering(); modify the accumulation logic in
VmAllocateHostFlow so that if the image is an ISO scenario (or image size is
zero) you use spec.getRootDiskOffering().getDiskSize() instead of
image.getSize() — i.e., check the image/ISO condition and prefer
rootDiskOffering for ISO cases to keep totalCacheSize /
volumeCache::requiredSize consistent with prepareMsg().

In `@header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java`:
- Around line 23-35: The current HostBlockDeviceStruct.equals() and hashCode()
treat all instances with wwid==null as equal; update both methods so equality
is: if wwid != null use wwid.equals(other.wwid), otherwise fall back to
comparing the stable secondary key composed of path and hctl (e.g.,
Objects.equals(path, other.path) && Objects.equals(hctl, other.hctl)), and make
hashCode return a combined hash of (wwid != null ? wwid : combine(path,hctl)) so
null WWIDs no longer collide; modify the equals(), hashCode() implementations in
HostBlockDeviceStruct accordingly and use null-safe utilities
(Objects.equals/Objects.hash).

In
`@header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy`:
- Around line 7-79: 当前文档仍然是占位模板:将 title 从模板文本替换为正确结构名(例如 "HostBlockDeviceStruct
结构体" 或项目中实际的结构名),并为每个 field(wwid, vendor, model, wwn, serial, hctl, type, path,
size, source, transport, targetIdentifier)补充简短的中文说明(描述字段含义、单位或示例值),保持 type 与
since 不变,确保所有 desc 字段非空并能帮助读者理解每个字段的用途。

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy`:
- Around line 9-49: Replace the placeholder title and fill missing descriptions:
update the title string to a meaningful Chinese name for the structure, add
concise desc texts for the fields poolUuid and installPath, and replace the
literal "null" descs in the ref blocks for cacheMode and state with short
explanatory phrases (or remove the desc property if no extra description is
needed); make these descriptions self-contained and under two lines where
possible and edit the blocks referencing VmLocalVolumeCacheInventory.cacheMode
and VmLocalVolumeCacheInventory.state, plus the field entries named poolUuid and
installPath, so the generated docs are complete and no placeholders remain.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java`:
- Around line 50-52: The code in VmLocalVolumeCachePoolInventory checks only for
null before deserializing vo.getMetadata(), which throws when metadata is an
empty or whitespace string; update the logic around vo.getMetadata() so you
first check for blank/empty (e.g., StringUtils.isBlank(vo.getMetadata()) or
vo.getMetadata() == null || vo.getMetadata().trim().isEmpty()) and only call
JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class) when the metadata
is non-blank, otherwise leave inv.setMetadata(null) or skip setting it; keep
references to vo.getMetadata(), inv.setMetadata(...), and
JSONObjectUtil.toObject(...) when making the change.

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java`:
- Around line 28-31: 注释中关于 VmLocalVolumeCachePoolVO 的模块归属描述不准确;打开
VmLocalVolumeCacheVO 类并编辑该注释(the javadoc block above the field referencing
VmLocalVolumeCachePoolVO)中移除或修正 “in kvm plugin” 的表述,改为要么仅保留“References
VmLocalVolumeCachePoolVO”要么替换为正确的位置说明(例如 “located in
header/localVolumeCache”),确保注释只引用 VmLocalVolumeCachePoolVO 并且不引入错误的模块归属信息。

In `@header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java`:
- Around line 218-219: The API currently lacks validation and trimming for
cache-related fields (APICreateVmInstanceMsg.rootVolumeCachePoolUuid and nested
DiskAO/VolumeCacheConfig.cachePoolUuid/cacheMode), so add resourceType to the
`@APIParam` on rootVolumeCachePoolUuid and any similar API params to enable
resource existence checks, then implement trimming and validation in
VmInstanceApiInterceptor.validate(APICreateVmInstanceMsg) to .trim() all
incoming UUID/string fields and verify cachePoolUuid exists and cacheMode is
within allowed values before constructing CreateVmInstanceMsg/VmInstanceSpec;
reference DiskAO and VolumeCacheConfig field names when validating nested
structures so malformed/whitespace values are rejected early.

In `@plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java`:
- Around line 43-48: In CacheTO.valueOf, avoid calling inv.getCacheMode().name()
directly to prevent NPE when cacheMode is null; check inv.getCacheMode() for
null first and only call name() when non-null, then pass the resulting String
(or null) to to.setCacheMode so existing non-null behavior is preserved; update
the logic around VmLocalVolumeCacheInventory.getCacheMode() and the
to.setCacheMode(...) call accordingly.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java`:
- Around line 5194-5267: Several new AgentCommand subclasses (InitPoolCmd,
ConnectPoolCmd, ExtendPoolCmd, DeletePoolCmd, CheckPoolCmd, GetPoolCapacityCmd,
GCPoolCmd, AllocateCacheCmd, DeleteCacheCmd, FlushCacheCmd, GetCacheCapacityCmd,
AttachVolumeCacheCmd, DetachVolumeCacheCmd) expose critical request fields
(poolUuid, mountPoint, instanceUuid, volume) without validation; add explicit
non-null/non-blank checks by implementing a validate() method on these command
classes (or a single protected validateRequired(String name, Object val) helper
on AgentCommand) that throws IllegalArgumentException when
poolUuid/mountPoint/instanceUuid are null/empty or when volume is null, override
where needed (e.g., GCPoolCmd checks volumes list), and ensure callers invoke
cmd.validate() before sending the command to the agent.

---

Nitpick comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java`:
- Around line 717-738: The handler for GetBlockDevicesOnHostMsg uses a generic
operr(...) when rsp.isSuccess() is false; add a dedicated KVM error code
(following the ORG_ZSTACK_KVM_xxxxx pattern used by other handlers) and return
that specific ErrorCode instead of the generic operr. Define a constant (e.g.,
ORG_ZSTACK_KVM_GET_BLOCK_DEVICES_ERROR) in the same error-constants class used
by other KVM handlers, construct an ErrorCode with that constant and the
rsp.getError() message in handle(GetBlockDevicesOnHostMsg msg) where
reply.setError(operr(...)) is currently used, and replace the operr call with
reply.setError(...) using the new ErrorCode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 4d6751cb-f8e7-460a-8f32-37cbed3d6c82

📥 Commits

Reviewing files that changed from the base of the PR and between 49191cf and 5c2d0ec.

⛔ Files ignored due to path filters (44)
  • conf/globalConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/persistence.xml is excluded by !**/*.xml
  • conf/serviceConfig/host.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCache.xml is excluded by !**/*.xml
  • conf/serviceConfig/vmLocalVolumeCachePool.xml is excluded by !**/*.xml
  • conf/springConfigXml/localVolumeCache.xml is excluded by !**/*.xml
  • conf/zstack.xml is excluded by !**/*.xml
  • sdk/src/main/java/SourceClassMap.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CacheTO.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateOvnControllerVmAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/HostInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeInventory.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/VolumeTO.java is excluded by !sdk/**
  • test/src/test/resources/springConfigXml/license.xml is excluded by !**/*.xml
📒 Files selected for processing (58)
  • compute/src/main/java/org/zstack/compute/host/HostBase.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmAllocateVolumeFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • conf/db/upgrade/V5.5.12__schema.sql
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java
  • header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/HostInventory.java
  • header/src/main/java/org/zstack/header/host/HostVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java
  • header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
  • header/src/main/java/org/zstack/header/volume/VolumeAO.java
  • header/src/main/java/org/zstack/header/volume/VolumeInventory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
  • plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java
  • storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java
  • testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
  • utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java
💤 Files with no reviewable changes (2)
  • storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java
🚧 Files skipped from review as they are similar to previous changes (18)
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/HostInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
  • header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy
  • compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java
  • compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java
  • header/src/main/java/org/zstack/header/host/HostVO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java
  • compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java

Comment on lines +157 to +161
Map<Integer, APICreateVmInstanceMsg.VolumeCacheConfig> dataDiskCacheConfigs = spec.getDataDiskCacheConfigOnIndex();
if (dataDiskCacheConfigs != null && !dataDiskCacheConfigs.isEmpty()) {
for (Integer index : dataDiskCacheConfigs.keySet()) {
APICreateVmInstanceMsg.VolumeCacheConfig dataDiskCacheConfig = dataDiskCacheConfigs.get(index);
totalCacheSize += spec.getDataDiskOfferings().get(index).getDiskSize();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

数据盘缓存容量的索引基准和卷创建流程不一致。

Line 157-161 这里按 spec.getDataDiskOfferings().get(index) 取大小,但同一份 dataDiskCacheConfigOnIndexcompute/src/main/java/org/zstack/compute/vm/VmAllocateVolumeFlow.java 的 Line 77-86 是按 dataVolumeSpecs 套用的。两边一旦出现非 offering 数据卷或索引不完全一致,这里就会把 requiredSize 算错,甚至直接 IndexOutOfBoundsException

🤖 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/VmAllocateHostFlow.java` around
lines 157 - 161, The code in VmAllocateHostFlow sums data disk sizes using
spec.getDataDiskOfferings().get(index).getDiskSize() while the allocation logic
in VmAllocateVolumeFlow uses spec.getDataVolumeSpecs() as the index basis; this
mismatch can produce wrong requiredSize or IndexOutOfBoundsException. Fix by
reading sizes from spec.getDataVolumeSpecs().get(index).getDiskSize() (or
otherwise map the same index space used by dataVolumeSpecs) when iterating
spec.getDataDiskCacheConfigOnIndex(), and add defensive checks that the
referenced dataVolumeSpecs entry exists and is non-null before accessing
getDiskSize() (fall back or skip if missing) so both flows use the same index
basis and avoid OOB errors.

Comment on lines +76 to +88
if (!spec.getDataDiskCacheConfigOnIndex().isEmpty() && !CollectionUtils.isEmpty(spec.getDataDiskOfferings())) {
List<VolumeSpec> dataVolumeSpecs = spec.getVolumeSpecs().stream()
.filter(s -> s.getType().equals(VolumeType.Data.toString())).collect(Collectors.toList());

IntStream.range(0, dataVolumeSpecs.size()).forEach(index -> {
APICreateVmInstanceMsg.VolumeCacheConfig config = spec.getDataDiskCacheConfigOnIndex().get(index);
if (config != null) {
dataVolumeSpecs.get(index).setEnableVolumeCache(true);
dataVolumeSpecs.get(index).setCachePoolUuid(config.getCachePoolUuid());
dataVolumeSpecs.get(index).setCacheMode(config.getCacheMode());
}
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

空指针会让未配置数据盘缓存的建机流程直接失败。

Line 76 这里先对 spec.getDataDiskCacheConfigOnIndex()isEmpty(),但同一 PR 的 compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java Line 157-158 已经按 nullable 处理了这个 getter。这里一旦返回 null,建机会在 volume allocation 阶段直接 NPE。

🛠 建议修改
-        if (!spec.getDataDiskCacheConfigOnIndex().isEmpty() && !CollectionUtils.isEmpty(spec.getDataDiskOfferings())) {
+        if (!MapUtils.isEmpty(spec.getDataDiskCacheConfigOnIndex()) && !CollectionUtils.isEmpty(spec.getDataDiskOfferings())) {
🤖 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/VmAllocateVolumeFlow.java` around
lines 76 - 88, In VmAllocateVolumeFlow, the call
spec.getDataDiskCacheConfigOnIndex().isEmpty() can NPE if the getter returns
null; change the guard to use a null-safe check (e.g.
!CollectionUtils.isEmpty(spec.getDataDiskCacheConfigOnIndex()) &&
!CollectionUtils.isEmpty(spec.getDataDiskOfferings())) before iterating, and
ensure the IntStream index loop only runs up to dataVolumeSpecs.size() and the
cache config list size (safeguard by checking both list sizes or using
min(dataVolumeSpecs.size(), spec.getDataDiskCacheConfigOnIndex().size())); keep
the existing null check for each config inside the loop.

Comment on lines +10 to +76
title "在这里输入结构的名称"

field {
name "uuid"
desc "资源的UUID,唯一标示该资源"
type "String"
since "5.5.6"
}
field {
name "hostUuid"
desc "物理机UUID"
type "String"
since "5.5.6"
}
field {
name "name"
desc "资源名称"
type "String"
since "5.5.6"
}
field {
name "description"
desc "资源的详细描述"
type "String"
since "5.5.6"
}
field {
name "metadata"
desc ""
type "LinkedHashMap"
since "5.5.6"
}
field {
name "totalCapacity"
desc ""
type "long"
since "5.5.6"
}
field {
name "availableCapacity"
desc ""
type "long"
since "5.5.6"
}
ref {
name "state"
path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.state"
desc "null"
type "VmLocalVolumeCachePoolState"
since "5.5.6"
clz VmLocalVolumeCachePoolState.class
}
ref {
name "status"
path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.status"
desc "null"
type "VmLocalVolumeCachePoolStatus"
since "5.5.6"
clz VmLocalVolumeCachePoolStatus.class
}
ref {
name "caches"
path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.caches"
desc "null"
type "Set"
since "5.5.6"
clz VmLocalVolumeCacheInventory.class
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

文档模板还没填完

Line 10 还是占位标题,后面多个 desc 为空字符串或 "null"。这些内容会直接进入生成文档,最终 API 文档基本不可用。提交前最好补成真实标题和字段说明。

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

In
`@header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy`
around lines 10 - 76, The document template currently contains placeholders: the
top title string and many field/ref desc values are empty or "null" (see title,
field descs for uuid, hostUuid, name, description, metadata, totalCapacity,
availableCapacity, and ref descs for state, status, caches in
VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy). Replace the placeholder title
with a real Chinese title for the VmLocalVolumeCachePoolInventory structure and
provide meaningful Chinese descriptions for each field and ref (e.g., uuid →
"资源的 UUID,唯一标识", hostUuid → "所属物理机的 UUID", metadata → "附加元数据键值对",
totalCapacity/availableCapacity → "总容量/可用容量,单位字节", refs state/status/caches →
short descriptions of enum meanings and cache set); ensure no desc remains empty
or "null" so generated API docs are complete.

Comment on lines +5319 to +5350
public CachePoolMetadata toCachePoolMetadata() {
CachePoolMetadata metadata = new CachePoolMetadata();
if (pvs != null) {
List<CachePoolMetadata.PVRef> pvRefs = pvs.stream().map(pv -> {
CachePoolMetadata.PVRef ref = new CachePoolMetadata.PVRef();
ref.setPvUuid(pv.pvUuid);
ref.setPvName(pv.pvName);
ref.setPvDevicePath(pv.pvDevicePath);
return ref;
}).collect(Collectors.toList());
metadata.setPvs(pvRefs);
}
if (vg != null) {
CachePoolMetadata.VGRef vgRef = new CachePoolMetadata.VGRef();
vgRef.setVgUuid(vg.vgUuid);
vgRef.setVgName(vg.vgName);
metadata.setVg(vgRef);
}
if (lv != null) {
CachePoolMetadata.LVRef lvRef = new CachePoolMetadata.LVRef();
lvRef.setLvUuid(lv.lvUuid);
lvRef.setLvName(lv.lvName);
lvRef.setLvPath(lv.lvPath);
metadata.setLv(lvRef);
}
if (filesystem != null) {
CachePoolMetadata.FileSystemRef fsRef = new CachePoolMetadata.FileSystemRef();
fsRef.setFsUuid(filesystem.fsUuid);
fsRef.setFsType(filesystem.fsType);
metadata.setFilesystem(fsRef);
}
return metadata;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

toCachePoolMetadata() 漏掉了 mountPoint

这里返回的 CachePoolMetadata 没有把 PoolRsp.mountPoint 拷进去,后续如果把这个对象重新序列化或持久化,挂载点会被静默丢失。

建议修改
         public CachePoolMetadata toCachePoolMetadata() {
             CachePoolMetadata metadata = new CachePoolMetadata();
+            metadata.setMountPoint(mountPoint);
             if (pvs != null) {
                 List<CachePoolMetadata.PVRef> pvRefs = pvs.stream().map(pv -> {
                     CachePoolMetadata.PVRef ref = new CachePoolMetadata.PVRef();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java` around lines
5319 - 5350, The toCachePoolMetadata() implementation omits copying the source
object's mountPoint into the returned CachePoolMetadata, causing the
PoolRsp.mountPoint to be lost; update toCachePoolMetadata() to read the
mountPoint field from the current object (e.g., mountPoint) and call
CachePoolMetadata.setMountPoint(...) on the created metadata before returning so
the mount point is preserved when serializing/persisting the CachePoolMetadata.

Comment on lines +610 to +624
VmLocalVolumeCacheVO cacheVO = null;
if (msg.getSystemTag(VolumeSystemTags.VOLUME_CACHE_ENABLE::isMatch) != null) {
cacheVO = new VmLocalVolumeCacheVO();
cacheVO.setUuid(Platform.getUuid());
cacheVO.setVolumeUuid(vo.getUuid());
cacheVO.setState(VmLocalVolumeCacheState.Uninstantiated);
String cacheModeTag = msg.getSystemTag(VolumeSystemTags.VOLUME_CACHE_MODE::isMatch);
String CachePoolTag = msg.getSystemTag(VolumeSystemTags.VOLUME_CACHE_POOL_UUID::isMatch);
if (cacheModeTag != null) {
cacheVO.setCacheMode(VmLocalVolumeCacheMode.valueOf(VolumeSystemTags.VOLUME_CACHE_MODE.getTokenByTag(cacheModeTag, VolumeSystemTags.CACHE_MODE_TOKEN)));
}
if (CachePoolTag != null) {
cacheVO.setPoolUuid(VolumeSystemTags.VOLUME_CACHE_POOL_UUID.getTokenByTag(CachePoolTag, VolumeSystemTags.CACHE_POOL_UUID_TOKEN));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

VmLocalVolumeCacheVO 的落库现在会留下半成功状态。

这里先创建 volume 和 volumeCache::* system tags,最后才单独 persist(cacheVO)。只要这一步因为外键或其他 DB 错误失败,请求会报错,但前面的 volume 与 tags 已经落库,后面会出现“tag 在、cache record 不在”的不一致。建议把 cache record 和 volume 放进同一事务,或者在失败分支补偿清理前面写入的记录。

Also applies to: 652-662

🤖 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/volume/VolumeManagerImpl.java`
around lines 610 - 624, The current flow in VolumeManagerImpl creates a Volume
and system tags then separately persists VmLocalVolumeCacheVO (when
VolumeSystemTags.VOLUME_CACHE_ENABLE matches), which can leave volume+tags
committed while persist(cacheVO) fails and causes inconsistency; modify the code
so that creation of the volume, the related system tags, and persisting
VmLocalVolumeCacheVO happen inside the same DB transaction (use the existing
database transaction helper / `@Transactional` block or
dbf.beginTransaction()/commit()) so either all succeed or all roll back, or if
you cannot transactionally group them, add a compensating cleanup on the
persist(cacheVO) failure path that removes the earlier-created volume and its
system tags to keep data consistent; key symbols to change: VolumeManagerImpl,
VmLocalVolumeCacheVO, VolumeSystemTags.VOLUME_CACHE_ENABLE, and the
persist(cacheVO) call.

Comment on lines +56 to +62
public static PatternedSystemTag VOLUME_CACHE_ENABLE = new PatternedSystemTag("volumeCache::enable", VolumeVO.class);

public static String CACHE_POOL_UUID_TOKEN = "poolUuid";
public static PatternedSystemTag VOLUME_CACHE_POOL_UUID = new PatternedSystemTag(String.format("volumeCache::%s::{%s}", CACHE_POOL_UUID_TOKEN, CACHE_POOL_UUID_TOKEN), VolumeVO.class);

public static String CACHE_MODE_TOKEN = "cacheMode";
public static PatternedSystemTag VOLUME_CACHE_MODE = new PatternedSystemTag(String.format("volumeCache::%s::{%s}", CACHE_MODE_TOKEN, CACHE_MODE_TOKEN ), VolumeVO.class);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

别让 volumeCache::* tags 被快照新卷继承。

storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java Line 867-875 在未显式传入 system tags 时会复制源 volume 的全部 system tags。这里新增的 poolUuid / cacheMode 是 host/pool 绑定配置,新卷继承后很容易带着过期缓存设置;而当前快照建卷路径又不会同步创建 VmLocalVolumeCacheVO。引入这些 tags 时需要同时把复制路径里的 volumeCache::* 过滤掉,或明确设计成不可继承。

🤖 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/volume/VolumeSystemTags.java` around
lines 56 - 62, The new patterned tags VOLUME_CACHE_ENABLE,
VOLUME_CACHE_POOL_UUID (uses CACHE_POOL_UUID_TOKEN) and VOLUME_CACHE_MODE (uses
CACHE_MODE_TOKEN) are host/pool binding settings and must not be inherited by
snapshot-created volumes; update the copy logic in VolumeManagerImpl (the code
path that copies system tags when creating a volume from snapshot) to filter out
any system tags whose token/key prefix matches "volumeCache::" (or explicitly
exclude VOLUME_CACHE_ENABLE, VOLUME_CACHE_POOL_UUID and VOLUME_CACHE_MODE) so
the snapshot->volume copy does not propagate these cache-specific tags, or
alternatively mark them as non-inheritable and ensure the copy routine respects
that flag.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants