Skip to content

<feature>[kvm]: periodic check and sync for VM host files#3693

Closed
zstack-robot-2 wants to merge 1 commit intofeature-zsv-5.0.0-vm-support-vtpm-and-secucebootfrom
sync/wenhao.zhang/zsv-ldap@@2
Closed

<feature>[kvm]: periodic check and sync for VM host files#3693
zstack-robot-2 wants to merge 1 commit intofeature-zsv-5.0.0-vm-support-vtpm-and-secucebootfrom
sync/wenhao.zhang/zsv-ldap@@2

Conversation

@zstack-robot-2
Copy link
Copy Markdown
Collaborator

Implement periodic VM host file checking with dirty detection and
force sync mechanism. This improves the sync interval from 30 minutes
to 15 seconds, allowing faster detection and sync of file changes.

Changes:

  • Add changeDate and lastSyncDate fields to VmHostFileVO for tracking
  • Add PeriodicDirtyCheck and PeriodicForceSync sync reasons
  • Add VM_HOST_FILE_CHANGED canonical event for file change reporting
  • Update VmHostFileTracker to check files periodically and sync when
    changed or force sync after long period without changes
  • Update vm.host.file.sync.interval default from 1800 to 15 seconds
  • Update config description from 'syncing' to 'checking'
  • Implement event listener to mark files as changed when reported

Resolves: ZSV-11371
Related: ZSV-11310

Change-Id: I6f62727061686b646f617463737672686d667578

sync from gitlab !9555

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Walkthrough

为 VmHostFile 添加两时间戳字段(changeDate、lastSyncDate),新增主机文件变更事件与数据契约,调整周期性按组检查/强制同步逻辑并携带 syncReason,修改 KVM 默认检查间隔及相关持久化与选择行为。

Changes

Cohort / File(s) Summary
数据库模式
conf/db/zsv/V5.0.0__schema.sql
zstack.VmHostFileVO 表新增可空时间戳列 changeDatelastSyncDate
事件与契约
header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
新增事件路径常量 VM_HOST_FILE_CHANGED_PATHKVM_REPORT_VM_HOST_FILE_CHANGED,并新增 VmHostFileChangedDatahostUuidvmUuidtypes)。
模型/元模型/SDK
header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java, header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java, header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java, sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
为 VM 主机文件实体、JPA 元模型与 SDK 实体增加 changeDatelastSyncDate 字段及相应 getter/setter/SingularAttribute;valueOf/toString 等处同步字段。
同步原因枚举
header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
新增枚举常量 PeriodicDirtyCheck("on periodic dirty check")PeriodicForceSync("on periodic force sync")
KVM 全局配置
plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
VM_HOST_FILE_SYNC_INTERVAL 默认值由 "1800" 改为 "15",描述由“同步”改为“检查”,key 与校验范围不变。
KVM 同步实现与跟踪器
plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java, plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java
使用单一请求时间戳驱动持久化(lastSyncDate、changeDate、lastOpDate、createDate);Tracker 注册事件监听并在事件到达时设置 changeDate;周期任务按 (vmUuid, hostUuid) 分组决定是 PeriodicDirtyCheck 还是 PeriodicForceSync,发送含 syncReason 的消息;克隆候选按 lastSyncDate 降序选择。

Sequence Diagram

sequenceDiagram
    participant HostAgent as Host Agent
    participant EventBus as Event Bus
    participant Tracker as VmHostFileTracker
    participant DB as Database

    Note over HostAgent,DB: 文件变更报告流程
    HostAgent->>EventBus: 发布 VM_HOST_FILE_CHANGED (hostUuid, vmUuid, types)
    EventBus->>Tracker: 触发变更处理
    Tracker->>DB: UPDATE VmHostFileVO SET changeDate=NOW WHERE hostUuid & vmUuid & type
    DB-->>Tracker: 返回更新结果

    Note over HostAgent,DB: 周期检查与同步流程
    Tracker->>DB: 查询分组记录(含 changeDate、lastSyncDate)
    DB-->>Tracker: 返回组内记录
    alt 发现 changeDate 非空(脏检查)
        Tracker->>HostAgent: 发送 SyncVmHostFilesFromHostMsg (syncReason=PeriodicDirtyCheck)
        HostAgent-->>Tracker: 同步结果
        Tracker->>DB: 清除组内 changeDate 并设置 lastSyncDate=NOW
        DB-->>Tracker: 更新完成
    else 未发现脏数据但超过阈值(强制)
        Tracker->>HostAgent: 发送 SyncVmHostFilesFromHostMsg (syncReason=PeriodicForceSync)
        HostAgent-->>Tracker: 同步结果
        Tracker->>DB: 设置组内 lastSyncDate=NOW
        DB-->>Tracker: 更新完成
    else 无需同步
        Tracker->>Tracker: 跳过该组
    end
Loading

Estimated code review effort

🎯 4 (复杂) | ⏱️ ~45 minutes

Poem

我是小兔报新潮,时间戳上蹦又跳,
事件轻敲门,脏点自己报,
定时检查与强制轮,记录更新好,🐰
主机文件稳又俏。

🚥 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 标题准确反映了拉取请求的主要变化:实现VM主机文件的定期检查和同步机制。
Description check ✅ Passed 描述与变更集相关,详细说明了实现内容、配置更改和新增功能,包括时间戳跟踪、同步原因和事件监听器。

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/wenhao.zhang/zsv-ldap@@2

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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/efi/KvmSecureBootManager.java (1)

294-314: ⚠️ Potential issue | 🟠 Major

syncToHostFiles 中对现有文件的 changeDate 设置可能有误。

对于现有文件(第 298 行),同步时设置了 changeDate = now,但未设置 lastSyncDate。对于新文件(第 311 行),设置了 lastSyncDate = now,但未设置 changeDate

根据 PR 描述的语义:

  • changeDate:文件被报告变更的时间(应由 VM_HOST_FILE_CHANGED 事件设置,同步后清除)
  • lastSyncDate:最后一次成功同步的时间

当前逻辑对于现有文件:同步完成后应该是 changeDate = null(表示不再脏),lastSyncDate = now(同步刚完成)。

建议修正:

🐛 建议修复
 if (fileExists) {
     SQL.New(VmHostFileVO.class)
             .eq(VmHostFileVO_.uuid, file.getUuid())
-            .set(VmHostFileVO_.changeDate, now)
+            .set(VmHostFileVO_.changeDate, null)
+            .set(VmHostFileVO_.lastSyncDate, now)
             .set(VmHostFileVO_.lastOpDate, now)
             .set(VmHostFileVO_.lastSyncReason, msg.getSyncReason())
             .update();
 }
🤖 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/efi/KvmSecureBootManager.java` around
lines 294 - 314, The current syncToHostFiles logic sets changeDate on existing
files and omits lastSyncDate, and when creating new VmHostFileVO it sets
lastSyncDate but not changeDate; update the existing-file update
(SQL.New(...).set(...)) to set VmHostFileVO_.changeDate to null and set
VmHostFileVO_.lastSyncDate to now (in addition to lastOpDate/changeDate
handling), and when constructing the new VmHostFileVO ensure
file.setChangeDate(null) and file.setLastSyncDate(now) so new entries and
updates reflect that changeDate is cleared after successful sync and
lastSyncDate records the sync time.
🧹 Nitpick comments (2)
header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java (1)

127-138: __example__() 方法未包含新字段的示例值。

changeDatelastSyncDate 字段未在 __example__() 方法中设置示例值,可能影响 API 文档的完整性。建议添加示例值以便 API 文档展示完整的字段结构。

📝 建议补充示例值
     public static VmHostFileInventory __example__() {
         VmHostFileInventory ref = new VmHostFileInventory();
         ref.setUuid(DocUtils.createFixedUuid(VmHostFileVO.class));
         ref.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class));
         ref.setHostUuid(DocUtils.createFixedUuid(HostVO.class));
         ref.setType(VmHostFileType.TpmState.toString());
         ref.setPath("/var/lib/libvirt/swtpm/" + ref.getHostUuid() + "/");
         ref.setLastSyncReason("on libvirt shutdown event");
+        ref.setChangeDate(null);  // null when file is not dirty
+        ref.setLastSyncDate(DocUtils.timestamp());
         ref.setCreateDate(DocUtils.timestamp());
         ref.setLastOpDate(DocUtils.timestamp());
         return ref;
     }
🤖 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/additions/VmHostFileInventory.java`
around lines 127 - 138, 在 VmHostFileInventory.__example__() 中补充缺失的示例字段:为
changeDate 和 lastSyncDate 赋示例时间值(例如使用 DocUtils.timestamp())以确保示例完整;在方法体内在设置
createDate/lastOpDate 的相同位置调用 ref.setChangeDate(...) 和
ref.setLastSyncDate(...),保持格式与现有字段一致并返回 ref。
plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (1)

178-178: 高频周期日志建议降级为 debug

Line 178 每 15 秒打印一次 info,在多管理节点下会产生明显日志噪音。建议改为 debug,仅在异常或统计汇总时保留 info

🤖 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/vmfiles/VmHostFileTracker.java` at
line 178, The periodic start-up log "VmHostFileTracker: starting periodic check
of VM host files" in class VmHostFileTracker is emitted every 15s and should be
downgraded from logger.info to logger.debug to avoid high-frequency noise;
locate the logger.info call with that exact message in VmHostFileTracker and
change it to logger.debug, leaving any remaining info-level logs for errors or
aggregated/statistical summaries only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java`:
- Around line 136-142: The loop over types calls VmHostFileType.valueOf(type)
which will throw on null/unknown values and abort processing; update the for
(String type : types) loop in VmHostFileTracker to validate and/or safely parse
each type (e.g., skip null/blank and use a try/catch or EnumUtils to map to
VmHostFileType) before calling
SQL.New(...).eq(...).eq(...).eq(VmHostFileVO_.type,
VmHostFileType.valueOf(type)).set(...).update(); ensure invalid entries are
logged and skipped so subsequent types continue to be processed.
- Around line 294-300: The current code unconditionally clears
VmHostFileVO.changeDate after a successful sync which can wipe out concurrent
dirty markers; modify the update to only clear changeDate if it still equals the
value that was present when the sync started: capture the file.getChangeDate()
(e.g., preSyncChangeDate) for each VmHostFileVO in the group and change the
SQL.New(...) update to include a conditional equality check on
VmHostFileVO_.changeDate == preSyncChangeDate (while still setting lastSyncDate
to syncTime), so only pre-existing dirty flags are cleared and concurrent
changes are preserved.

---

Outside diff comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java`:
- Around line 294-314: The current syncToHostFiles logic sets changeDate on
existing files and omits lastSyncDate, and when creating new VmHostFileVO it
sets lastSyncDate but not changeDate; update the existing-file update
(SQL.New(...).set(...)) to set VmHostFileVO_.changeDate to null and set
VmHostFileVO_.lastSyncDate to now (in addition to lastOpDate/changeDate
handling), and when constructing the new VmHostFileVO ensure
file.setChangeDate(null) and file.setLastSyncDate(now) so new entries and
updates reflect that changeDate is cleared after successful sync and
lastSyncDate records the sync time.

---

Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java`:
- Around line 127-138: 在 VmHostFileInventory.__example__() 中补充缺失的示例字段:为
changeDate 和 lastSyncDate 赋示例时间值(例如使用 DocUtils.timestamp())以确保示例完整;在方法体内在设置
createDate/lastOpDate 的相同位置调用 ref.setChangeDate(...) 和
ref.setLastSyncDate(...),保持格式与现有字段一致并返回 ref。

In `@plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java`:
- Line 178: The periodic start-up log "VmHostFileTracker: starting periodic
check of VM host files" in class VmHostFileTracker is emitted every 15s and
should be downgraded from logger.info to logger.debug to avoid high-frequency
noise; locate the logger.info call with that exact message in VmHostFileTracker
and change it to logger.debug, leaving any remaining info-level logs for errors
or aggregated/statistical summaries only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 30bf3749-2669-4183-9393-b1799f7dec29

📥 Commits

Reviewing files that changed from the base of the PR and between 912b02f and e6948f8.

📒 Files selected for processing (9)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

Comment on lines +294 to +300
Timestamp syncTime = new Timestamp(timeHelper.getCurrentTimeMillis());
for (VmHostFileVO file : group) {
SQL.New(VmHostFileVO.class)
.eq(VmHostFileVO_.uuid, file.getUuid())
.set(VmHostFileVO_.changeDate, null)
.set(VmHostFileVO_.lastSyncDate, syncTime)
.update();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

同步成功后无条件清空 changeDate,会丢失并发变更标记

Line 298 在异步回调里直接把 changeDate 置空;如果同步进行期间又收到一次 VM_HOST_FILE_CHANGED 事件,新脏标记会被覆盖,导致后续周期检查漏同步。建议仅清除“同步开始前”的脏标记。

可选修复示例
             SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg();
@@
             bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID);
+            Timestamp syncStartTime = new Timestamp(timeHelper.getCurrentTimeMillis());
             bus.send(syncMsg, new CloudBusCallBack(whileCompletion) {
                 `@Override`
                 public void run(MessageReply reply) {
@@
                     } else {
                         Timestamp syncTime = new Timestamp(timeHelper.getCurrentTimeMillis());
                         for (VmHostFileVO file : group) {
                             SQL.New(VmHostFileVO.class)
                                     .eq(VmHostFileVO_.uuid, file.getUuid())
-                                    .set(VmHostFileVO_.changeDate, null)
                                     .set(VmHostFileVO_.lastSyncDate, syncTime)
                                     .update();
+
+                            SQL.New(VmHostFileVO.class)
+                                    .eq(VmHostFileVO_.uuid, file.getUuid())
+                                    .notNull(VmHostFileVO_.changeDate)
+                                    .lte(VmHostFileVO_.changeDate, syncStartTime)
+                                    .set(VmHostFileVO_.changeDate, null)
+                                    .update();
                         }
                     }
🤖 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/vmfiles/VmHostFileTracker.java`
around lines 294 - 300, The current code unconditionally clears
VmHostFileVO.changeDate after a successful sync which can wipe out concurrent
dirty markers; modify the update to only clear changeDate if it still equals the
value that was present when the sync started: capture the file.getChangeDate()
(e.g., preSyncChangeDate) for each VmHostFileVO in the group and change the
SQL.New(...) update to include a conditional equality check on
VmHostFileVO_.changeDate == preSyncChangeDate (while still setting lastSyncDate
to syncTime), so only pre-existing dirty flags are cleared and concurrent
changes are preserved.

@MatheMatrix MatheMatrix force-pushed the sync/wenhao.zhang/zsv-ldap@@2 branch 2 times, most recently from d27b9d6 to 207d0b7 Compare April 7, 2026 09:14
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java`:
- Around line 45-48: The changeDate dirty-flag is being unconditionally cleared
in VmHostFileTracker (the sync callback that writes back sync results), which
can race with concurrent event-thread updates and lose new dirty marks; modify
the write-back logic in VmHostFileTracker to perform a CAS-style conditional
update: first read the current VmHostFileVO.changeDate value (capturing null as
a distinct value), then issue an update that sets changeDate = NULL only if the
row's changeDate still equals the previously read value (use the DB/update API
that can compare-to-value or a WHERE clause comparing changeDate, and when
comparing to NULL use an isNull() check), so changeDate is cleared only when
unchanged since read.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a7edbf8e-be95-4bcf-8523-45eb409d0d52

📥 Commits

Reviewing files that changed from the base of the PR and between e6948f8 and d27b9d6.

📒 Files selected for processing (10)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java
  • sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
✅ Files skipped from review due to trivial changes (4)
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

Comment on lines +45 to +48
private Timestamp changeDate;
@Column
private Timestamp lastSyncDate;
@Column
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 目的:确认是否存在“无条件清空 changeDate”的更新语句
# 期望:如果看到 set(changeDate, null) 且附近没有基于旧值/IS NULL 的条件,则可复现该风险
rg -n -C4 'set\(VmHostFileVO_\.changeDate,\s*null\)|eq\(VmHostFileVO_\.changeDate|isNull\(VmHostFileVO_\.changeDate' \
  plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

Repository: MatheMatrix/zstack

Length of output: 629


🏁 Script executed:

#!/bin/bash
# 1. 查看 VmHostFileVO 中 changeDate 的定义和注释
echo "=== VmHostFileVO changeDate 定义 ==="
rg -n -B2 -A2 'changeDate' header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java

# 2. 查看 VmHostFileTracker 的 135-152 行(置脏逻辑)
echo -e "\n=== VmHostFileTracker 135-152 行(置脏逻辑)==="
sed -n '130,160p' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

# 3. 查看 changeDate 设置的其他地方
echo -e "\n=== changeDate 在 VmHostFileTracker 中的所有出现 ==="
rg -n 'changeDate' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

Repository: MatheMatrix/zstack

Length of output: 2434


🏁 Script executed:

#!/bin/bash
# 1. 查看同步检查逻辑:如何使用 changeDate 判断是否需要同步
echo "=== changeDate 在检查或过滤中的使用 ==="
rg -n 'changeDate|isNotNull|getChangeDate' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

# 2. 查看同步任务的主方法和事务结构
echo -e "\n=== 同步任务主方法(查找 PeriodicTask 或主查询逻辑)==="
sed -n '160,300p' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java

# 3. 查看是否有数据库事务或锁
echo -e "\n=== 检查同步操作是否在事务中 ==="
rg -n -B3 'SQL.New\(VmHostFileVO' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java | head -40

Repository: MatheMatrix/zstack

Length of output: 6540


🏁 Script executed:

#!/bin/bash
# 1. 查看 forceSyncThresholdMs 的计算和默认值
echo "=== VM_HOST_FILE_SYNC_INTERVAL 配置 ==="
rg -n 'VM_HOST_FILE_SYNC_INTERVAL' plugin/kvm/src/main/java/org/zstack/kvm/ -A2 -B2 | head -50

# 2. 确认 changeDate = null 时是否需要特殊的 IS NULL 处理
echo -e "\n=== 数据库中 changeDate 字段定义 ==="
rg -n 'changeDate' plugin/kvm/src/main/resources/db -r -A1 -B1

# 3. 查看是否有其他地方使用了类似的 CAS 模式进行字段更新
echo -e "\n=== 查看库中 CAS 更新的例子 ==="
rg -n '\.eq.*\(.*\).*\.eq' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/ | head -10

Repository: MatheMatrix/zstack

Length of output: 2749


🏁 Script executed:

#!/bin/bash
# 1. 查看 changeDate 是否在其他地方被使用
echo "=== changeDate 在整个 KVM 模块中的使用情况 ==="
rg -n 'changeDate' plugin/kvm/src/main/java/org/zstack/kvm/ -g '*.java' | grep -v 'VmHostFileTracker\|VmHostFileVO'

# 2. 查看是否有类似的 CAS 更新模式
echo -e "\n=== 查看 ZStack 中条件更新(WHERE子句带多个条件)的例子 ==="
rg -n '\.eq.*\.eq.*\.set' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/ | head -5

# 3. 确认脏标记是否有其他初始化或检查逻辑
echo -e "\n=== 新增文件时 changeDate 的初始化 ==="
rg -n 'new VmHostFileVO\|setChangeDate' plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/ -B2 -A2

Repository: MatheMatrix/zstack

Length of output: 170


🏁 Script executed:

#!/bin/bash
# 1. 确认 SQL 框架如何处理 null 比较
echo "=== 查看 SQL 框架对 null 值的处理 ==="
rg -n 'isNull|IS NULL' plugin/kvm/src/main/java/org/zstack/kvm/ -A1 | head -20

# 2. 查看是否有 isNull 或类似的方法
echo -e "\n=== 在 VmHostFileTracker 或相邻文件中查找 isNull 用法 ==="
fd -t f "*.java" plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/ | xargs rg -l 'isNull' 2>/dev/null

# 3. 快速验证数据库迁移脚本中的字段类型
echo -e "\n=== 查找数据库架构定义或迁移脚本 ==="
find . -name "*schema*" -o -name "*upgrade*" | grep -i sql | head -10

Repository: MatheMatrix/zstack

Length of output: 2982


🏁 Script executed:

#!/bin/bash
# 查看 isNull 的具体用法
echo "=== isNull 的具体用法 ==="
rg -n 'isNull' core/src/main/java/org/zstack/core/db/UpdateQueryImpl.java -B2 -A2 | head -30

# 查看是否有 IS NULL 的示例
echo -e "\n=== IS NULL 在代码中的出现 ==="
rg -n 'IS NULL' plugin/kvm/src/main/java/org/zstack/kvm/ -B1 -A1 | head -20

# 查看 VmHostFileVO 的数据库迁移脚本
echo -e "\n=== 查找 VmHostFile 表的创建脚本 ==="
rg -n 'CREATE TABLE.*VmHostFile|vm_host_file' conf/db/ -A5 | head -30

Repository: MatheMatrix/zstack

Length of output: 1412


🏁 Script executed:

#!/bin/bash
# 查看完整的 VmHostFileVO 表定义,包括 changeDate 字段
echo "=== VmHostFileVO 完整表定义 ==="
sed -n '12,40p' conf/db/zsv/V5.0.0__schema.sql

# 查看是否有新增 changeDate 字段的迁移脚本
echo -e "\n=== 查找新增 changeDate 的迁移脚本 ==="
rg -n 'changeDate|lastSyncDate' conf/db/ -A2 -B2 | head -50

Repository: MatheMatrix/zstack

Length of output: 3032


避免并发覆盖 changeDate 脏标记,防止安全文件同步延迟。

changeDate 被用于脏检测机制(null = 无变更,非 null = 有变更),但当前同步器在成功回写时存在无条件清空(plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java:298),与事件线程置脏并发时会覆盖新变更。这导致脏标记丢失,虽然系统有 25 分钟强制同步补救,但对 TPM/Secure Boot 等安全关键文件延迟不可接受。

建议改为条件更新(CAS):仅当 changeDate 仍是读取时的值时才清空。由于字段支持 NULL,需使用 isNull() 方法处理:

🔧 建议修复(VmHostFileTracker 同步回写处)
  for (VmHostFileVO file : group) {
-     SQL.New(VmHostFileVO.class)
-             .eq(VmHostFileVO_.uuid, file.getUuid())
-             .set(VmHostFileVO_.changeDate, null)
-             .set(VmHostFileVO_.lastSyncDate, syncTime)
-             .update();
+     // 条件更新:仅当 changeDate 仍未被并发置脏时才清空
+     UpdateQuery query = SQL.New(VmHostFileVO.class)
+             .eq(VmHostFileVO_.uuid, file.getUuid());
+     
+     if (file.getChangeDate() == null) {
+         query.isNull(VmHostFileVO_.changeDate);
+     } else {
+         query.eq(VmHostFileVO_.changeDate, file.getChangeDate());
+     }
+     
+     query.set(VmHostFileVO_.changeDate, null)
+          .set(VmHostFileVO_.lastSyncDate, syncTime)
+          .update();
  }
🤖 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/additions/VmHostFileVO.java` around
lines 45 - 48, The changeDate dirty-flag is being unconditionally cleared in
VmHostFileTracker (the sync callback that writes back sync results), which can
race with concurrent event-thread updates and lose new dirty marks; modify the
write-back logic in VmHostFileTracker to perform a CAS-style conditional update:
first read the current VmHostFileVO.changeDate value (capturing null as a
distinct value), then issue an update that sets changeDate = NULL only if the
row's changeDate still equals the previously read value (use the DB/update API
that can compare-to-value or a WHERE clause comparing changeDate, and when
comparing to NULL use an isNull() check), so changeDate is cleared only when
unchanged since read.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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/vmfiles/VmHostFileTracker.java (1)

249-279: ⚠️ Potential issue | 🟠 Major

把 dirty/force-sync 过滤下推到查询层。

现在是先把所有运行态 VM 的 VmHostFileVO 全量查出并分组,再在内存里判断 changeDate/lastSyncDate。默认 15 秒周期下,这会把 DB 扫描和对象分配放大很多倍,即使没有任何脏文件也一样。建议在初始查询里直接过滤 changeDate != nulllastSyncDate is nulllastSyncDate < forceSyncBefore,只拉取需要处理的 group。

🤖 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/vmfiles/VmHostFileTracker.java`
around lines 249 - 279, The current logic pulls all VmHostFileVO for running VMs
into memory and then filters by changeDate/lastSyncDate, causing excessive DB
scans and allocations; change the initial query that builds groups to only
return rows matching "changeDate IS NOT NULL OR lastSyncDate IS NULL OR
lastSyncDate < :forceSyncBefore" (where forceSyncBefore = new Timestamp(now -
forceSyncThresholdMs)), so grouping (the variable groups used by the While<>),
VmHostFileVO objects, and subsequent checks in the While step only iterate over
candidates that actually need sync; keep the existing checks around
destinationMaker.isManagedByUs(vmUuid) and the VmHostFileSyncReason selection
but remove the in-memory filter that tests changeDate/lastSyncDate since it will
be handled by the query.
♻️ Duplicate comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (1)

282-296: ⚠️ Potential issue | 🔴 Critical

同步请求缺少 pre-sync 标记,仍然会丢并发变更。

这条链路现在依赖 changeDate 做 dirty-check,但这里发送消息时没有把“同步开始前”的标记带下去。下游 plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java:294-302 成功后仍会直接把 changeDate 清空;如果 host 文件在本次读取之后、清理之前再次变更,新脏标记会被覆盖,下一轮周期检查就可能漏同步。建议把每个文件的 pre-sync changeDate 或统一的 syncStartTime 传给下游,并在清理时做条件更新。

🤖 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/vmfiles/VmHostFileTracker.java`
around lines 282 - 296, The SyncVmHostFilesFromHostMsg sent from
VmHostFileTracker currently omits any pre-sync marker, so capture and include a
pre-sync timestamp (e.g., syncStartTime) or each file's preSync changeDate when
building SyncVmHostFilesFromHostMsg (set a new field like syncStartTime on
SyncVmHostFilesFromHostMsg and set it alongside setNvRamPath/setTpmStateFolder
for the files in the group) and pass it downstream; then update
KvmSecureBootManager (the code that clears changeDate) to only clear changeDate
conditionally using that provided pre-sync marker (i.e., only clear if DB
changeDate equals the pre-sync value or is <= syncStartTime) to avoid losing
concurrent updates.
🧹 Nitpick comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (1)

186-188: 把这条周期日志降到 debug

默认周期已经变成 15 秒,这里会在每个管理节点持续打 info,即使本轮没有任何候选文件也是如此。建议只在实际进入同步时保留 info,空轮询改成 debug

🤖 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/vmfiles/VmHostFileTracker.java`
around lines 186 - 188, The periodic startup log in VmHostFileTracker.run()
currently logs at info for every 15s tick; change
logger.info("VmHostFileTracker: starting periodic check of VM host files") to
logger.debug(...) so empty polls don't flood info logs, and move or add an
info-level log inside syncVmHostFiles() (or where actual work begins) to record
when a real sync starts; update references in VmHostFileTracker.run() and
syncVmHostFiles() accordingly.
🤖 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/vmfiles/VmHostFileTracker.java`:
- Around line 249-279: The current logic pulls all VmHostFileVO for running VMs
into memory and then filters by changeDate/lastSyncDate, causing excessive DB
scans and allocations; change the initial query that builds groups to only
return rows matching "changeDate IS NOT NULL OR lastSyncDate IS NULL OR
lastSyncDate < :forceSyncBefore" (where forceSyncBefore = new Timestamp(now -
forceSyncThresholdMs)), so grouping (the variable groups used by the While<>),
VmHostFileVO objects, and subsequent checks in the While step only iterate over
candidates that actually need sync; keep the existing checks around
destinationMaker.isManagedByUs(vmUuid) and the VmHostFileSyncReason selection
but remove the in-memory filter that tests changeDate/lastSyncDate since it will
be handled by the query.

---

Duplicate comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java`:
- Around line 282-296: The SyncVmHostFilesFromHostMsg sent from
VmHostFileTracker currently omits any pre-sync marker, so capture and include a
pre-sync timestamp (e.g., syncStartTime) or each file's preSync changeDate when
building SyncVmHostFilesFromHostMsg (set a new field like syncStartTime on
SyncVmHostFilesFromHostMsg and set it alongside setNvRamPath/setTpmStateFolder
for the files in the group) and pass it downstream; then update
KvmSecureBootManager (the code that clears changeDate) to only clear changeDate
conditionally using that provided pre-sync marker (i.e., only clear if DB
changeDate equals the pre-sync value or is <= syncStartTime) to avoid losing
concurrent updates.

---

Nitpick comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java`:
- Around line 186-188: The periodic startup log in VmHostFileTracker.run()
currently logs at info for every 15s tick; change
logger.info("VmHostFileTracker: starting periodic check of VM host files") to
logger.debug(...) so empty polls don't flood info logs, and move or add an
info-level log inside syncVmHostFiles() (or where actual work begins) to record
when a real sync starts; update references in VmHostFileTracker.run() and
syncVmHostFiles() accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ca75f24-2ef7-4b4e-aba2-092540745e64

📥 Commits

Reviewing files that changed from the base of the PR and between d27b9d6 and 207d0b7.

📒 Files selected for processing (10)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java
  • sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
✅ Files skipped from review due to trivial changes (4)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java

Implement periodic VM host file checking with dirty detection and
force sync mechanism. This improves the sync interval from 30 minutes
to 15 seconds, allowing faster detection and sync of file changes.

Changes:
- Add changeDate and lastSyncDate fields to VmHostFileVO for tracking
- Add PeriodicDirtyCheck and PeriodicForceSync sync reasons
- Add VM_HOST_FILE_CHANGED canonical event for file change reporting
- Update VmHostFileTracker to check files periodically and sync when
  changed or force sync after long period without changes
- Update vm.host.file.sync.interval default from 1800 to 15 seconds
- Update config description from 'syncing' to 'checking'
- Implement event listener to mark files as changed when reported

Resolves: ZSV-11371
Related: ZSV-11310

Change-Id: I6f62727061686b646f617463737672686d667578
@MatheMatrix MatheMatrix force-pushed the sync/wenhao.zhang/zsv-ldap@@2 branch from 207d0b7 to 195a8e0 Compare April 7, 2026 09:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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/vmfiles/VmHostFileTracker.java (1)

264-292: ⚠️ Potential issue | 🟠 Major

不要让历史 VmHostFileVO 参与本轮 current-file 判定。

这里直接对整个 group 计算 hasChanged / oldestLastSync,但 syncMsg 最终每种类型只会带一个 path。只要组里残留一条同 (vmUuid, hostUuid, type) 的历史记录且它的 changeDate != nulllastSyncDate == null/过旧,这个 VM 就会反复命中 PeriodicDirtyCheck / PeriodicForceSync,而这一轮同步又未必会更新到那条旧记录。建议先按 type 收敛到 current/latest 记录,再决定 syncReason 并组装消息。

💡 一个可行的收敛方式
+            Map<VmHostFileType, VmHostFileVO> latestByType = new HashMap<>();
+            for (VmHostFileVO file : group) {
+                VmHostFileVO current = latestByType.get(file.getType());
+                if (current == null || current.getLastOpDate().before(file.getLastOpDate())) {
+                    latestByType.put(file.getType(), file);
+                }
+            }
+            Collection<VmHostFileVO> currentFiles = latestByType.values();
-
-            boolean hasChanged = group.stream().anyMatch(f -> f.getChangeDate() != null);
+            boolean hasChanged = currentFiles.stream().anyMatch(f -> f.getChangeDate() != null);
 ...
-                Timestamp oldestLastSync = group.stream()
+                Timestamp oldestLastSync = currentFiles.stream()
 ...
-            for (VmHostFileVO file : group) {
+            for (VmHostFileVO file : currentFiles) {

前面的事件置脏逻辑也要对齐到同一条 current/latest 记录,否则旧行还是会把这组持续判成 dirty。

🤖 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/vmfiles/VmHostFileTracker.java`
around lines 264 - 292, 当前代码在 VmHostFileTracker 中对整个 group 直接计算
hasChanged/oldestLastSync 导致历史记录(旧的 VmHostFileVO)把当前文件组误判为需要同步;请先基于
VmHostFileType 收敛到每种 type 的“current/latest” VmHostFileVO(例如按最后更新时间/lastSyncDate
或 id 选最大者)生成一个 deduped 列表,然后在该 deduped 列表上重新计算 hasChanged、oldestLastSync 和决定
syncReason,再用同一 deduped 列表为 SyncVmHostFilesFromHostMsg 填充 nvRam/tpm
路径(涉及符号:VmHostFileVO、VmHostFileType、VmHostFileTracker、SyncVmHostFilesFromHostMsg、hasChanged、oldestLastSync、syncReason)。同时确保前端将某
VM 标记为 dirty 的逻辑也只对这同一条 current/latest 记录生效。
🧹 Nitpick comments (1)
plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (1)

186-188: 把这条周期日志降到 debug 或做限频。

默认间隔已经改成 15 秒,这里的 info 会稳定刷屏,线上很容易淹没真正异常。更合适的是 debug,或者只在本轮实际触发 dirty/force-sync 时再输出 info

♻️ 一个最小改动
-                logger.info("VmHostFileTracker: starting periodic check of VM host files");
+                logger.debug("VmHostFileTracker: starting periodic check of VM host files");
🤖 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/vmfiles/VmHostFileTracker.java`
around lines 186 - 188, 将 VmHostFileTracker 中匿名 Runnable 的开头日志从持续性的 logger.info
降级为 debug 或限频输出:在 run() 方法里把 logger.info("VmHostFileTracker: starting periodic
check of VM host files") 改为 logger.debug(...),或者仅在 syncVmHostFiles() 实际触发
dirty/force-sync 时才调用 logger.info。定位符:匿名 Runnable 的 run()、类 VmHostFileTracker
和方法 syncVmHostFiles();可实现方式:1) 直接改为 logger.debug;或 2) 修改 syncVmHostFiles()
返回布尔/状态表示是否有变更,然后在 run() 中仅当返回 true(或状态包含 dirty/force-sync)时才记录 info 级别日志。
🤖 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/vmfiles/VmHostFileTracker.java`:
- Around line 264-292: 当前代码在 VmHostFileTracker 中对整个 group 直接计算
hasChanged/oldestLastSync 导致历史记录(旧的 VmHostFileVO)把当前文件组误判为需要同步;请先基于
VmHostFileType 收敛到每种 type 的“current/latest” VmHostFileVO(例如按最后更新时间/lastSyncDate
或 id 选最大者)生成一个 deduped 列表,然后在该 deduped 列表上重新计算 hasChanged、oldestLastSync 和决定
syncReason,再用同一 deduped 列表为 SyncVmHostFilesFromHostMsg 填充 nvRam/tpm
路径(涉及符号:VmHostFileVO、VmHostFileType、VmHostFileTracker、SyncVmHostFilesFromHostMsg、hasChanged、oldestLastSync、syncReason)。同时确保前端将某
VM 标记为 dirty 的逻辑也只对这同一条 current/latest 记录生效。

---

Nitpick comments:
In `@plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java`:
- Around line 186-188: 将 VmHostFileTracker 中匿名 Runnable 的开头日志从持续性的 logger.info
降级为 debug 或限频输出:在 run() 方法里把 logger.info("VmHostFileTracker: starting periodic
check of VM host files") 改为 logger.debug(...),或者仅在 syncVmHostFiles() 实际触发
dirty/force-sync 时才调用 logger.info。定位符:匿名 Runnable 的 run()、类 VmHostFileTracker
和方法 syncVmHostFiles();可实现方式:1) 直接改为 logger.debug;或 2) 修改 syncVmHostFiles()
返回布尔/状态表示是否有变更,然后在 run() 中仅当返回 true(或状态包含 dirty/force-sync)时才记录 info 级别日志。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fa93547d-edae-4a2b-8338-9729ed468986

📥 Commits

Reviewing files that changed from the base of the PR and between 207d0b7 and 195a8e0.

📒 Files selected for processing (10)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java
  • plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java
  • sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
✅ Files skipped from review due to trivial changes (4)
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java
  • sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • conf/db/zsv/V5.0.0__schema.sql
  • header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java
  • plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java

@zstack-robot-2
Copy link
Copy Markdown
Collaborator Author

Comment from yaohua.wu:

🔍 Code Review — <feature>[kvm]: periodic check and sync for VM host files

关联 Issue: ZSV-11371 / ZSV-11310
目标分支: feature-zsv-5.0.0-vm-support-vtpm-and-secuceboot
变更范围: 10 个文件,涉及 DB schema、VO/Inventory、枚举、canonical event、Tracker 核心逻辑、GlobalConfig、SDK

总体评价

本 MR 将 VM 主机文件的同步模式从"定时全量同步(30min 间隔)"改为"定时脏检查(15s 间隔)+ 变动同步 + 长时间无变动强制同步",大幅减少同步延迟。整体架构设计合理,CAS 清除 changeDate 的竞态处理亮点明显。以下提出几点关注:


🟡 Warning: 强制同步阈值为硬编码魔数

文件: plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (约 line 249)

long forceSyncThresholdMs = checkIntervalMs * 100;

强制同步阈值 = 检查间隔 × 100(默认 15s × 100 = 25 分钟)。这个 100 倍乘数没有配套注释,也不可配置。

建议

  • 至少加一行注释说明设计意图(如:"如果连续 100 个检查周期无变动,触发一次强制同步以防万一"
  • 或将其提取为 named constant:private static final int FORCE_SYNC_MULTIPLIER = 100;
  • 更进一步可考虑作为 GlobalConfig 供运维调整

🟡 Warning: 历史 VmHostFileVO 记录可能干扰脏检查判定

文件: plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (约 line 261-278)

当前按 vmUuid::hostUuid 分组后,在整个 group 上判断 hasChanged 和计算 oldestLastSync

boolean hasChanged = group.stream().anyMatch(f -> f.getChangeDate() != null);
Timestamp oldestLastSync = group.stream()
    .map(VmHostFileVO::getLastSyncDate)
    .min(Comparator.nullsFirst(Comparator.naturalOrder()))
    .orElse(null);

如果同一 (vmUuid, hostUuid, type) 存在多条记录(如历史残留),其中一条旧记录的 changeDate != nulllastSyncDate == null/过旧,就会持续触发同步,但 SyncVmHostFilesFromHostMsg 每种 type 只带一个 path——该轮同步不一定会更新到那条旧记录,从而形成"每 15 秒都命中 → 同步 → 旧行未清理 → 下轮又命中"的循环。

建议:先按 type 收敛到当前/最新的记录(如按 lastOpDate 取最新一条),再做脏检查和组装消息。事件置脏逻辑 setupCanonicalEvents() 里也建议同样只更新 current 记录。


🟡 Warning: 事件标记脏数据更新了同类型全部记录

文件: plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java (约 line 146-155)

long updated = SQL.New(VmHostFileVO.class)
    .eq(VmHostFileVO_.hostUuid, hostUuid)
    .eq(VmHostFileVO_.vmInstanceUuid, vmUuid)
    .eq(VmHostFileVO_.type, fileType)
    .set(VmHostFileVO_.changeDate, now)
    .update();

此 SQL 会将同 (hostUuid, vmUuid, type)所有 记录标记为脏,包括历史行。如果历史行未在 sync 中被清理,它们的 changeDate 也不会被 CAS 清除(因为 CAS 清除只作用于 uuid 匹配的记录),进一步加剧上条 Warning 的循环问题。

建议:加上 orderByDesc(VmHostFileVO_.lastOpDate).limit(1) 或先查出 current 记录的 uuid 再做定向更新。


🟢 Suggestion: KVM_REPORT_VM_HOST_FILE_CHANGED 在本 diff 中未被使用

文件: header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java (line 23)

public static final String KVM_REPORT_VM_HOST_FILE_CHANGED = "/kvm/reportvmhostfilechanged";

该常量在本 MR 的 diff 中没有任何引用方。如果是为后续 KVM Agent 上报预留,建议加注释说明用途;如果已在其他 MR / 模块中使用,请确认已合并或同步。


🟢 Suggestion: syncToHostFilesSQLBatch 的使用

文件: plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java (约 line 300-314)

原先单次 SQL.New(...).update() 被替换为 SQLBatch,目的是先更新 lastSyncDate 再 CAS 清除 changeDate。设计正确,但 SQLBatch 默认不在同一事务里运行(需确认 ZStack SQLBatch 的事务语义)。如果两个 SQL 不在同一事务内,存在极小概率窗口期。建议确认 SQLBatch.execute() 是否保证事务原子性,或改用显式事务。


✅ 亮点

  1. CAS 清除 changeDate (lt(VmHostFileVO_.changeDate, syncTime)):优雅地避免了"同步过程中新变更被意外清除"的竞态。
  2. timeHelper.getCurrentTimeMillis() 在同步开始前取时间戳并贯穿使用,保证了同一轮同步内时间一致性。
  3. orderByDesc 从 lastOpDate 改为 lastSyncDate:更精确地反映"最近一次成功同步"的语义。
  4. 枚举、VO、Inventory、SDK 各层同步更新,变更完整。

🤖 Robot Reviewer

@MatheMatrix
Copy link
Copy Markdown
Owner

Comment from wenhao.zhang:

(vmUuid, hostUuid, type) 数据库中有唯一键

UNIQUE KEY ukVmHostFileVO (vmInstanceUuid, hostUuid, type)

@MatheMatrix MatheMatrix closed this Apr 7, 2026
@zstack-robot-1 zstack-robot-1 deleted the sync/wenhao.zhang/zsv-ldap@@2 branch April 7, 2026 14:48
@MatheMatrix MatheMatrix restored the sync/wenhao.zhang/zsv-ldap@@2 branch April 8, 2026 02:39
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