Skip to content

<fix>[vm]: validate local storage mount point#4345

Open
ZStack-Robot wants to merge 1 commit into
zsv_5.1.0from
sync/tao.gan/ZSV-6589
Open

<fix>[vm]: validate local storage mount point#4345
ZStack-Robot wants to merge 1 commit into
zsv_5.1.0from
sync/tao.gan/ZSV-6589

Conversation

@ZStack-Robot

Copy link
Copy Markdown
Collaborator

MountBlockDevice now rejects a mount point that is already used by local primary storage attached to the host's cluster, so the API fails before mounting or formatting the block device.

Add cluster lookup to ProxyHardwareFactory and KVMHostFactory.
Validate APIMountBlockDeviceMsg mountPoint before proxy hardware retrieval.
Check existing local primary storage URLs in the resolved cluster and return API error on conflict.

Resolves: ZSV-6589

Change-Id: I747a706370786d6b75666b756c6d7469706b636d

sync from gitlab !10306

MountBlockDevice now rejects a mount point that is already used by local primary storage attached to the host's cluster, so the API fails before mounting or formatting the block device.

Add cluster lookup to ProxyHardwareFactory and KVMHostFactory.
Validate APIMountBlockDeviceMsg mountPoint before proxy hardware retrieval.
Check existing local primary storage URLs in the resolved cluster and return API error on conflict.

Resolves: ZSV-6589

Change-Id: I747a706370786d6b75666b756c6d7469706b636d
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

APIMountBlockDeviceMsg 拦截流程中新增校验逻辑:通过 ProxyHardwareFactory 获取宿主机所属集群 UUID,查询该集群内的本地主存储 URL,与归一化后的挂载点对比,若冲突则抛出拦截异常。同时移除挂载点末尾斜杠的限制规则。

Changes

挂载点与本地主存储冲突校验

Layer / File(s) Summary
ProxyHardwareFactory 接口扩展与 KVM 实现
header/.../ProxyHardwareFactory.java, plugin/kvm/.../KVMHostFactory.java
ProxyHardwareFactory 新增 default 方法 getClusterUuid(String hostName) 返回 nullKVMHostFactory 覆写该方法,通过 managementIp 过滤条件查询 HostVO 返回 clusterUuid
拦截器挂载点占用校验
compute/.../HostApiInterceptor.java
新增 validateMountPointNotOccupiedByLocalStorage,查询集群内本地主存储 URL 并与归一化挂载点对比,冲突时抛出异常;新增 normalizeMountPoint 去除路径末尾斜杠;调整 validate 方法中路径与挂载点校验的调用顺序;移除 validateMountPoint 中末尾斜杠的额外限制。

估计代码审查工作量

🎯 3 (Moderate) | ⏱️ ~20 minutes

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 标题准确概括了本次变更的核心:校验本地存储挂载点冲突。
Description check ✅ Passed 描述与代码变更一致,说明了冲突校验、集群查询和相关处理流程。
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/tao.gan/ZSV-6589

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

@zstack-robot-2

Copy link
Copy Markdown
Collaborator

Comment from yaohua.wu:

Review: MR !10306 — ZSV-6589

目标分支: zsv_5.1.0(base da7f310a 与目标分支 HEAD 一致,无需 rebase)
变更范围: HostApiInterceptor.javaProxyHardwareFactory.javaKVMHostFactory.java

总体评价

变更与 Jira ZSV-6589 及甘涛在 issue 中确认的最终方案一致:在 APIMountBlockDeviceMsg 拦截层校验挂载路径是否已被同集群本地主存储 URL 占用,占用即报错。模块/分层选择正确(API 拦截层),查询范围限定为 host 所在 cluster 的 LocalStorage,路径比较做了末尾 / 兼容,整体设计合理。结论 APPROVED(含若干需关注项)

另外发现一处正向改进:原代码 validate(APIMountBlockDeviceMsg)msg.getPassword() != null提前 return,导致带密码的请求会跳过 validatePath/validateMountPoint。本 MR 把三项校验前置到方法开头无条件执行,顺带修掉了这个校验绕过。这是好的改动,但属于行为变更——此前带密码且路径非法的请求会被放过,现在会被拒绝,请确认无存量调用方依赖旧行为。


🟡 Warning

1. 删除了 mountPoint 末尾 / 的拒绝校验,属于不必要的行为回退
compute/.../HostApiInterceptor.java validateMountPoint() 删除了:

if (mountPoint.endsWith("/") && !mountPoint.equals("/")) {
    throw new ApiMessageInterceptionException(operr(
            "mountPoint should not end with '/' except root directory"));
}

本次目标只需要在比较时把 /zstack_ps/zstack_ps/ 视为等价,而这只需对已存在的 local storage URLnormalizeMountPoint 即可——msg.getMountPoint() 本身在保留该拒绝校验的前提下永远不会以 / 结尾,无需归一化。删除这条 guard 后,形如 /vms_ds/ 的入参会通过校验并带着末尾 / 进入后续真实 SSH mount 流程。原作者刻意加这条限制,通常是因为下游 mount/卸载/路径拼接对末尾 / 敏感。建议:保留对入参 mountPoint 的末尾 / 拒绝校验,仅对查询出的 local storage URL 做归一化比较;若确认下游已能正确处理末尾 /,请在 MR 描述中说明删除理由。

2. normalizeMountPoint(url) 对 null 的 LocalStorage URL 存在 NPE 风险

private static String normalizeMountPoint(String path) {
    while (path.length() > 1 && path.endsWith("/")) { ... }   // path 为 null 时 NPE
}

msg.getMountPoint() 已被前置的 validateMountPoint 保证非空,安全;但循环中对 localStorageUrls 里每个 url 调用该方法,若某条 LocalStorage 的 url 为 null 会抛 NPE。LocalStorage 正常情况下 url 必填,概率较低,但建议加一道 url == null 跳过的防御,避免脏数据导致 API 异常。


🟢 Suggestion

3. 校验仅对「已注册到集群的 KVM 物理机」生效,存在覆盖盲区
getClusterUuid 只在 KVMHostFactory 实现,非 KVM 的 ProxyHardwareFactory 走 default 返回 null → 跳过校验;同时当 hostName 对应的 host 尚未加入集群(clusterUuid == null)时也会静默跳过。对于「图形化添加本地存储」场景物理机通常已在集群内,符合预期,但请确认产品侧不存在「host 未注册即可 mount」的路径,否则该校验会被绕过。

4. import 顺序
新增的 org.zstack.header.storage.primary.*java.util.List 被插在 org.zstack.utils.* 之后,分组略乱。建议按包名分组排序,纯属风格问题。

5. TOCTOU(信息项,非阻塞)
API 拦截层校验与真实 mount 之间存在时间窗,两个并发的相同路径 mount 请求可能都通过校验。这是 zstack API 层校验的通用限制,本场景可接受,无需在本 MR 处理。


验证建议

  • 新增/修改逻辑后建议补一条稳定性用例覆盖「同集群已有 LocalStorage URL 占用 → MountBlockDevice 报错」与「末尾 / 归一化等价」两种路径。
  • 确认 PremiumTestCaseStabilityTest / 相关 SubCase 通过框架完整执行。

🤖 Robot Reviewer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 `@compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java`:
- Around line 262-270: The mount point comparison in
HostApiInterceptor.validateMountPoint only trims trailing slashes, so paths
containing "." can bypass the local storage conflict check; update
normalizeMountPoint and the compare flow so both msg.getMountPoint() and each
PrimaryStorageVO_.url are fully normalized before equality checks. Use the
existing validateMountPoint and normalizeMountPoint logic to ensure both values
resolve to the same canonical path representation, then compare the normalized
results in the loop over localStorageUrls.
🪄 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: 22003620-0e41-4bdf-9e7d-349ced8c6485

📥 Commits

Reviewing files that changed from the base of the PR and between da7f310 and 5e8c408.

📒 Files selected for processing (3)
  • compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java
  • header/src/main/java/org/zstack/header/agent/ProxyHardwareFactory.java
  • plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java

Comment on lines +262 to +270
String mountPoint = normalizeMountPoint(msg.getMountPoint());
List<String> localStorageUrls = Q.New(PrimaryStorageVO.class)
.select(PrimaryStorageVO_.url)
.eq(PrimaryStorageVO_.type, PrimaryStorageConstants.LOCAL_STORAGE_TYPE)
.in(PrimaryStorageVO_.uuid, primaryStorageUuids)
.listValues();

for (String url : localStorageUrls) {
if (!mountPoint.equals(normalizeMountPoint(url))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java"

echo "== file line count =="
wc -l "$file"

echo "== relevant slices =="
sed -n '220,320p' "$file" | cat -n

echo "== usages of normalizeMountPoint =="
rg -n "normalizeMountPoint|validateMountPoint|MountPoint" "$file"

Repository: MatheMatrix/zstack

Length of output: 4014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== search for path normalization helpers =="
rg -n "Paths\.get\(|normalize\(\)|canonicali[sz]e|normalizeMountPoint" compute/src/main/java

echo "== inspect validateMountPoint area =="
sed -n '210,285p' compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java | cat -n

Repository: MatheMatrix/zstack

Length of output: 4326


补全 mount point 规范化,避免 . 路径段绕过本地主存储占用检查

validateMountPoint 允许 .,但 normalizeMountPoint 只去掉尾部 /;像 /local_ps/./data 这类路径会和 /local_ps/data 比较不相等,却可能落到同一目录。这里应把 msg.getMountPoint() 和已存在的 url 统一做路径归一化后再比对。

🤖 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 `@compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java` around
lines 262 - 270, The mount point comparison in
HostApiInterceptor.validateMountPoint only trims trailing slashes, so paths
containing "." can bypass the local storage conflict check; update
normalizeMountPoint and the compare flow so both msg.getMountPoint() and each
PrimaryStorageVO_.url are fully normalized before equality checks. Use the
existing validateMountPoint and normalizeMountPoint logic to ensure both values
resolve to the same canonical path representation, then compare the normalized
results in the loop over localStorageUrls.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants