Skip to content

<fix>[storage]: ZSTAC-86038 recover stale XInfini image cache#4538

Open
zstack-robot-2 wants to merge 1 commit into
5.5.28from
sync/jin.ma/fix/ZSTAC-86038
Open

<fix>[storage]: ZSTAC-86038 recover stale XInfini image cache#4538
zstack-robot-2 wants to merge 1 commit into
5.5.28from
sync/jin.ma/fix/ZSTAC-86038

Conversation

@zstack-robot-2

Copy link
Copy Markdown
Collaborator

Summary

修复 XInfini 主存储的 ImageCacheVO 仍存在、但其后端 BsSnap 已丢失时,创建 vhost-root 云主机直接复用 stale cache 并报 BsSnap not found 的问题。

Changes

  • 在既有 image UUID + primary storage UUID 串行任务内,参考 Ceph cache download 逻辑检查后端 bits
  • XInfini 按 install path 精确检查 volume 或 snapshot;仅明确 404 触发普通 cache 清理与重建
  • 检查、删除遇到非 404 错误时保留 ImageCacheVO并向上传播
  • 保护 volumeSnapshotReuse://:复用快照缺失时明确失败,不删除用户源卷
  • simulator 补齐真实 BsSnap not found 行为,并增加 stale cache 自愈与复用快照安全回归场景

Testing

  • Rebase 后编译:mvn compile -pl header,storage,plugin/xinfini -am -DskipTests(24/24 modules)
  • XinfiniPrimaryStorageCase:1 test, 0 failures, 0 errors, 0 skipped
  • 真实双 MN / XInfini E2E:旧 cache xinfini://2/17239@5640 检查到 404 后重建为 xinfini://2/17302@5648,VM 成功进入 Running
  • 临时 VM/根卷已清理,双 MN 已恢复原始 JAR并确认 Running
  • CI pipeline

Resolves: ZSTAC-86038

sync from gitlab !10500

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Could not fetch remote config from http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml: TimeoutError: The operation was aborted due to timeout
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Changes

存储资源统计与快照处理

Layer / File(s) Summary
存储资源统计契约
header/src/main/java/org/zstack/header/storage/addon/StorageResource.java, header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java, header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java, header/src/main/java/org/zstack/header/volume/VolumeStats.java
新增统一的 StorageResource 抽象类型,卷和快照统计类改为继承该类型,统计接口返回类型改为 StorageResource
控制器统计分流
plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java, plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java, plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java, plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniApiHelper.java
各存储控制器根据安装路径区分卷与快照统计;XInfini 对已删除资源返回资源不存在错误。
外部主存储缓存与容量处理
storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java
镜像缓存、backing chain、卷大小同步和模板估算改用 StorageResource,并处理缺失资源时的删除与重试。
集成测试与模拟器校验
test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/XinfiniPrimaryStorageCase.groovy, testlib/src/main/java/org/zstack/testlib/ExternalPrimaryStorageSpec.groovy
新增缺失快照、reused 快照保护及卷快照统计测试;模拟器在克隆源快照不存在时返回 404,并继承源快照大小。

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExternalPrimaryStorage
  participant PrimaryStorageControllerSvc
  participant StorageController
  participant ImageCacheVO
  ExternalPrimaryStorage->>ImageCacheVO: 选择候选镜像缓存
  ExternalPrimaryStorage->>PrimaryStorageControllerSvc: stats(installPath)
  PrimaryStorageControllerSvc->>StorageController: 查询卷或快照统计
  StorageController-->>PrimaryStorageControllerSvc: StorageResource
  PrimaryStorageControllerSvc-->>ExternalPrimaryStorage: 返回统计结果
  alt 资源不存在
    ExternalPrimaryStorage->>StorageController: 删除卷和快照
    ExternalPrimaryStorage->>ImageCacheVO: 删除缓存记录并重试
  end
Loading

Poem

我是小兔,抱来资源新框,
卷与快照共用一张网。
缺失快照不乱跑,
缓存重建有方向。
测试萝卜咔嚓响!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了修复 XInfini 旧 ImageCache 失效后重建缓存的主要变更。
Description check ✅ Passed 描述与代码变更一致,涵盖 stale cache 重建、404 处理、快照复用保护和回归测试。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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/jin.ma/fix/ZSTAC-86038

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

@MatheMatrix MatheMatrix force-pushed the sync/jin.ma/fix/ZSTAC-86038 branch from 50d955c to 049cc6e Compare July 10, 2026 11:17
Validate cached XInfini snapshots before reuse. Rebuild missing regular caches and preserve user-owned snapshot reuse sources.

Resolves: ZSTAC-86038

Change-Id: I88b8ac1fa8740194d735aa2c0dd139cadbd119ff
@MatheMatrix MatheMatrix force-pushed the sync/jin.ma/fix/ZSTAC-86038 branch from 049cc6e to c18971f Compare July 12, 2026 20:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java`:
- Around line 1149-1172: 在 ExponStorageController.stats 的快照分支中补充 parentUri:使用
snap.getPoolName() 和 snap.getVolumeId() 还原父卷路径并设置到 VolumeSnapshotStats,确保
getParentChain() 能继续遍历父卷;保持现有快照统计字段和普通卷分支不变。
🪄 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: 2578f0ea-9c91-4e95-9bba-97fcac899fb1

📥 Commits

Reviewing files that changed from the base of the PR and between 6e0a2ea and c18971f.

📒 Files selected for processing (11)
  • header/src/main/java/org/zstack/header/storage/addon/StorageResource.java
  • header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java
  • header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotStats.java
  • header/src/main/java/org/zstack/header/volume/VolumeStats.java
  • plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
  • plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniApiHelper.java
  • plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
  • plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java
  • storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java
  • test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/XinfiniPrimaryStorageCase.groovy
  • testlib/src/main/java/org/zstack/testlib/ExternalPrimaryStorageSpec.groovy

Comment on lines 1149 to 1172
@Override
public void stats(String installPath, ReturnValueCompletion<VolumeStats> comp) {
public void stats(String installPath, ReturnValueCompletion<StorageResource> comp) {
if (installPath.contains("@")) {
VolumeSnapshotModule snap = apiHelper.getVolumeSnapshot(getSnapIdFromPath(installPath));
VolumeSnapshotStats stats = new VolumeSnapshotStats();
stats.setInstallPath(installPath);
stats.setSize(snap.getSnapSize());
stats.setActualSize(snap.getDataSize());
comp.success(stats);
return;
}

comp.success(getVolumeStats(installPath));
}

private VolumeStats getVolumeStats(String installPath) {
VolumeModule vol = apiHelper.getVolume(getVolIdFromPath(installPath));
VolumeStats stats = new VolumeStats();
stats.setInstallPath(installPath);
stats.setSize(vol.getVolumeSize());
stats.setActualSize(vol.getDataSize());
stats.setFormat(VolumeConstant.VOLUME_FORMAT_RAW);
comp.success(stats);
return stats;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether Expon's VolumeModule exposes a backing snapshot id equivalent to XInfini's getBsSnapId()
ast-grep run --pattern 'class VolumeModule { $$$ }' --lang java plugin/expon
rg -n 'class VolumeModule' -A40 plugin/expon

Repository: MatheMatrix/zstack

Length of output: 4089


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Expon volume/snapshot models and the parent-chain logic referenced by the comment.
files=(
  plugin/expon/src/main/java/org/zstack/expon/sdk/volume/VolumeModule.java
  plugin/expon/src/main/java/org/zstack/expon/sdk/volume/snapshot/VolumeSnapshotModule.java
  plugin/expon/src/main/java/org/zstack/storage/primary/ExternalPrimaryStorage.java
  plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
  plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
)

for f in "${files[@]}"; do
  echo "===== $f ====="
  if [ -f "$f" ]; then
    wc -l "$f"
    echo "--- outline ---"
    ast-grep outline "$f" --view expanded | sed -n '1,220p'
  else
    echo "MISSING"
  fi
  echo
done

Repository: MatheMatrix/zstack

Length of output: 9477


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any parentUri / backing snapshot related fields or accessors in Expon SDK and controller code.
rg -n "parentUri|getParentUri|getBsSnapId|backing|snapshot" plugin/expon/src/main/java plugin/xinfini/src/main/java plugin/storage/src/main/java 2>/dev/null || true

Repository: MatheMatrix/zstack

Length of output: 11651


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the exact implementation slices around the relevant methods.
sed -n '1138,1180p' plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
printf '\n===== XInfini =====\n'
sed -n '1120,1185p' plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
printf '\n===== ExternalPrimaryStorage =====\n'
rg -n "getParentChain|parentUri" plugin/storage/src/main/java/org/zstack/storage/primary/ExternalPrimaryStorage.java

Repository: MatheMatrix/zstack

Length of output: 2964


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the parent-chain implementation and any callers to see whether Expon stats are expected to provide parentUri.
rg -n "getParentChain\\(|parentUri == null|setParentUri\\(|getParentUri\\(" plugin -g '*.java'

Repository: MatheMatrix/zstack

Length of output: 1594


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the parent-chain traversal logic and the storage stats interface.
files=(
  plugin/storage/src/main/java/org/zstack/storage/primary/ExternalPrimaryStorage.java
  plugin/storage/src/main/java/org/zstack/storage/primary/ExternalPrimaryStorageFactory.java
  plugin/storage/src/main/java/org/zstack/storage/primary/ExternalPrimaryStorageBase.java
)

for f in "${files[@]}"; do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    rg -n "getParentChain|parentUri|StorageResource|VolumeStats|VolumeSnapshotStats" "$f"
  fi
done

Repository: MatheMatrix/zstack

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file that contains the parent-chain logic.
fd -a 'ExternalPrimaryStorage.java|ExternalPrimaryStorageBase.java|ExternalPrimaryStorageFactory.java' plugin

Repository: MatheMatrix/zstack

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the entire repository for the parent-chain implementation and related stats logic.
rg -n "getParentChain\\(|parentUri == null|setParentUri\\(|getParentUri\\(" .

Repository: MatheMatrix/zstack

Length of output: 2600


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Expon snapshot/volume SDK models and the API helper getter to see whether a backing parent can be derived.
for f in \
  plugin/expon/src/main/java/org/zstack/expon/sdk/volume/VolumeSnapshotModule.java \
  plugin/expon/src/main/java/org/zstack/expon/sdk/volume/VolumeModule.java \
  plugin/expon/src/main/java/org/zstack/expon/ExponApiHelper.java
do
  echo "===== $f ====="
  wc -l "$f"
  echo "--- outline ---"
  ast-grep outline "$f" --view expanded | sed -n '1,220p'
  echo
done

Repository: MatheMatrix/zstack

Length of output: 10359


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant Expon slices around getVolumeStats(), stats(), and any snapshot/volume parent handling nearby.
sed -n '1138,1180p' plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
printf '\n===== VolumeSnapshotModule =====\n'
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/sdk/volume/VolumeSnapshotModule.java
printf '\n===== ExponApiHelper#getVolumeSnapshot =====\n'
rg -n "public VolumeSnapshotModule getVolumeSnapshot|public VolumeModule getVolume\\(" -A25 -B5 plugin/expon/src/main/java/org/zstack/expon/ExponApiHelper.java

Repository: MatheMatrix/zstack

Length of output: 7474


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any Expon field or method that could identify a volume's backing snapshot / clone source.
rg -n "bsSnapId|backing|clone|source snapshot|sourceSnapshot|parentUri" plugin/expon/src/main/java/org/zstack/expon -g '*.java'

Repository: MatheMatrix/zstack

Length of output: 2237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Expon volume/snapshot API payload shapes to see whether source/backing info is available anywhere else.
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/sdk/volume/GetVolumeResponse.java
printf '\n===== GetVolumeSnapshotResponse =====\n'
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/sdk/volume/GetVolumeSnapshotResponse.java
printf '\n===== GetVolumeSnapshotRequest =====\n'
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/sdk/volume/GetVolumeSnapshotRequest.java

Repository: MatheMatrix/zstack

Length of output: 3825


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Expon path builders and whether snapshot stats could be linked to a volume path directly.
rg -n "buildExponVolumePath|buildExponSnapshotPath|getSnapIdFromPath|getVolIdFromPath|setParentUri\\(" plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
sed -n '1,260p' plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java | sed -n '1,220p'

Repository: MatheMatrix/zstack

Length of output: 11971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the helper methods around install path construction and path parsing in ExponStorageController.
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java

Repository: MatheMatrix/zstack

Length of output: 9374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether Expon already has a volume-path helper and how clone/snapshot methods populate parentUri.
rg -n "buildExponVolumePath|buildExponSnapshotPath|setParentUri\\(" plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
printf '\n===== clone / snapshot slices =====\n'
sed -n '1096,1338p' plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java

Repository: MatheMatrix/zstack

Length of output: 10106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the parent-chain traversal to confirm what parentUri is used for.
sed -n '1688,1745p' storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java

Repository: MatheMatrix/zstack

Length of output: 2508


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether Expon's snapshot response fields can be mapped to a parent storage path.
rg -n "getVolumeId\\(|buildExponPath\\(|buildExponSnapshotPath\\(" plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java plugin/expon/src/main/java/org/zstack/expon/ExponNameHelper.java
sed -n '1,220p' plugin/expon/src/main/java/org/zstack/expon/ExponNameHelper.java

Repository: MatheMatrix/zstack

Length of output: 3853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any helper that can reconstruct a volume path from a volume ID, which would be needed to set parentUri for snapshots.
rg -n "buildExponPath\\(|buildExponSnapshotPath\\(" plugin/expon/src/main/java/org/zstack/expon -g '*.java'

Repository: MatheMatrix/zstack

Length of output: 1567


补上快照 parentUri
stats() 的快照分支已经能从 snap.getPoolName() / snap.getVolumeId() 还原父卷路径,但现在没设置 parentUrigetParentChain() 会在这一层直接截断。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java`
around lines 1149 - 1172, 在 ExponStorageController.stats 的快照分支中补充 parentUri:使用
snap.getPoolName() 和 snap.getVolumeId() 还原父卷路径并设置到 VolumeSnapshotStats,确保
getParentChain() 能继续遍历父卷;保持现有快照统计字段和普通卷分支不变。

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