<fix>[vm]: add MetadataImpact#3603
Conversation
Walkthrough新增完整云主机元数据子系统:模型、JPA/DB 表、全局配置、扩展点、消息/API/事件/文档、主存储(Local/NFS)后端与 KVM agent 交互、SDK、测试模拟器、GC 与流程整合(扫描/获取/更新/清理/注册/路径替换/重试)。 Changes
Sequence Diagram(s)sequenceDiagram
participant API as API
participant Flow as VmExpungeMetadataFlow
participant DB as Database
participant Bus as CloudBus
participant PS as PrimaryStorageService
participant Host as HostAgent
API->>Flow: 调用清理/注册/扫描流程
Flow->>DB: 查询 VM/volume/ps 信息
Flow->>Bus: 发送 Cleanup/Get/Scan/Update 消息(目标 primaryStorageUuid)
Bus->>PS: 路由到对应 PrimaryStorage 服务
PS->>Host: HTTP 调用主机 Agent(get/scan/cleanup/write/rebase)
Host-->>PS: 返回命令响应(metadata / entries / rebasedCount)
PS-->>Bus: 回复 MessageReply(成功或 ErrorCode)
Bus-->>Flow: 回调处理(成功记录/失败提交 GC)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
|
Comment from yaohua.wu: Review: MR !9463 — ZSV-11559 【注册虚拟机】MetadataImpact (zstack)
🔴 Critical1. [VmInstanceMetadataConstants.java] 该方法硬编码扫描 建议:从 MN 侧传入明确的 PS mount path 前缀,而非从文件路径反推。或至少从路径末端向前扫描(最后一个 marker 之前的部分),减小误匹配概率。 2. [VmExpungeMetadataFlow.java] Lines 159-163: 建议:添加 null check,若 hostUuid 为 null 则 log warn 并跳过 cleanup(metadata 文件会随 PS 目录清理自然消失),或直接提交 GC 延迟处理。 3. [APICreateSystemTagMsg/APICreateSystemTagsMsg] System tag 可以应用于任何资源类型(Host、Image、Network 等),不仅仅是 VM 及其关联资源。每次创建 system tag 时都会触发 resolver 做 DB 查询(检查 VM → Volume → NIC 链路),对非 VM 相关的高频 tag 操作造成不必要的性能开销。 建议:在 interceptor 层增加 🟡 Warning4. [V5.0.0__schema.sql] SQL migration 幂等性
5. [VmGlobalConfig.java] 6 个新 GlobalConfig 的默认值合理性
6. [VmMetadataFingerprintVO.java]
7. [@MetadataImpact resolver] resolver 名称为 magic string,无编译期校验
建议:定义 8. [多个 API 消息文件] 部分文件 diff 为空 约 20+ 个文件标记为 MOD 但 diff 内容为空(如 🟢 Suggestion9. [VmMetadataErrors.java] 错误码格式 使用 10. [VmMetadataScanEntry.java] Scan 返回的 11. [CleanupVmInstanceMetadataOnPrimaryStorageGC.java] GC 设计 GC 在 架构评价整体设计合理:
主要风险点在于 Verdict: REVISION_REQUIREDCritical #1 (extractOldPrefix 误匹配风险) 和 #2 (hostUuid null 未保护) 需要修复后再合入。 🤖 Robot Reviewer |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
100-141:⚠️ Potential issue | 🔴 Critical把 4 个 metadata 本地消息也接到
handleLocalMessage(),否则新增 handler 不可达。这里目前只把
RebaseVolumeBackingFileOnPrimaryStorageMsg加进了分发链,Update/Get/Scan/CleanupVmInstanceMetadataOnPrimaryStorageMsg都没有对应分支。发到NfsPrimaryStorage的这四类本地消息会继续落到super.handleLocalMessage(msg),下面新增的处理逻辑实际上不会执行。🐛 建议修复
} else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) { handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) { + handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg); + } else if (msg instanceof GetVmInstanceMetadataFromPrimaryStorageMsg) { + handle((GetVmInstanceMetadataFromPrimaryStorageMsg) msg); + } else if (msg instanceof ScanVmInstanceMetadataFromPrimaryStorageMsg) { + handle((ScanVmInstanceMetadataFromPrimaryStorageMsg) msg); + } else if (msg instanceof CleanupVmInstanceMetadataOnPrimaryStorageMsg) { + handle((CleanupVmInstanceMetadataOnPrimaryStorageMsg) msg); } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg);Also applies to: 1933-2061
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java` around lines 100 - 141, The message dispatch in NfsPrimaryStorage.handleLocalMessage currently routes many message types but misses four metadata local messages—UpdateVmInstanceMetadataOnPrimaryStorageMsg, GetVmInstanceMetadataFromPrimaryStorageMsg, ScanVmInstanceMetadataOnPrimaryStorageMsg, CleanupVmInstanceMetadataOnPrimaryStorageMsg—so their handlers never run; update handleLocalMessage to add else-if branches for each of these four message classes and call the existing handle(...) methods for them (matching the pattern used for RebaseVolumeBackingFileOnPrimaryStorageMsg and others) so those messages are dispatched to their handlers instead of falling through to super.handleLocalMessage(msg).
🧹 Nitpick comments (12)
header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java (1)
7-20: 建议将字段uuid重命名为vmInstanceUuid以提高代码可读性。当前字段名
uuid语义不够明确,而接口方法getVmInstanceUuid()清楚表明这是 VM 实例的 UUID。将字段名与接口方法保持一致,可以使代码更具自解释性。♻️ 建议的重构
public class ReadVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage { - private String uuid; + private String vmInstanceUuid; - public String getUuid() { - return uuid; + public String getVmInstanceUuid() { + return vmInstanceUuid; } - public void setUuid(String uuid) { - this.uuid = uuid; + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; } - `@Override` - public String getVmInstanceUuid() { - return uuid; - } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java` around lines 7 - 20, Rename the private field uuid in ReadVmInstanceMetadataMsg to vmInstanceUuid and update the corresponding accessor methods: change getUuid() to return vmInstanceUuid (or keep getUuid() as a delegating method if needed for binary compatibility), change setUuid(String) to setVmInstanceUuid(String) (or adapt setUuid to delegate), and update getVmInstanceUuid() to return vmInstanceUuid; ensure all references inside ReadVmInstanceMetadataMsg and any usages elsewhere are updated to the new field/method names (getVmInstanceUuid, setVmInstanceUuid, vmInstanceUuid) so the field name matches the interface method for clarity.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java (3)
18-18: 建议将code字段声明为final。该字段在构造器中被赋值后不再修改,应该标记为
final以确保不可变性和线程安全性。♻️ 建议的修改
- private String code; + private final String code;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java` at line 18, 将 VmMetadataErrors 类中的私有字段 code 声明为 final,因为它仅在构造器中赋值且之后不再修改;在 VmMetadataErrors 的字段定义处将 "private String code;" 改为 "private final String code;" 并确保所有构造器对 code 的赋值保持不变(如果存在默认构造器需改为显式赋值或移除),以保证不可变性和线程安全性。
11-11: 考虑优化METADATA_VM_REGISTERING的命名。该常量名称看起来更像描述一个状态("VM 正在注册中")而非错误条件。其他错误常量如
METADATA_INVALID_FORMAT、METADATA_NOT_FOUND等都明确表示错误场景。如果该错误码的含义是"由于 VM 正在注册而导致操作不被允许",可以考虑使用更明确的名称,例如:
METADATA_VM_REGISTRATION_IN_PROGRESSMETADATA_OPERATION_BLOCKED_BY_REGISTRATION这样能更清晰地表达错误语义。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java` at line 11, Rename the enum constant METADATA_VM_REGISTERING to a clearer error name that expresses the blocking-by-registration semantics (e.g., METADATA_VM_REGISTRATION_IN_PROGRESS or METADATA_OPERATION_BLOCKED_BY_REGISTRATION); update the enum declaration (replace METADATA_VM_REGISTERING(1307) with the chosen identifier) and then search & update all references/usages of METADATA_VM_REGISTERING across the codebase (tests, logging, exception throws, switch statements) to use the new constant name so compilation and behavior remain consistent.
3-15: 建议为枚举类及常量添加 Javadoc 注释。该枚举位于 header 包中,属于公共 API 的一部分。根据编码规范,公共 API 应配有有效的 Javadoc 注释以便生成 API 文档。建议:
- 为
VmMetadataErrors枚举添加类级别的 Javadoc,说明该枚举的用途- 为各个错误常量添加注释,说明每个错误码的含义及触发场景
这将有助于 API 文档的生成和开发者理解。
📝 Javadoc 示例
+/** + * VM 实例元数据相关的错误码定义。 + * <p> + * 每个错误码对应一个特定的元数据操作失败场景。 + */ public enum VmMetadataErrors { + /** 元数据格式无效 */ METADATA_INVALID_FORMAT(1300), + /** 元数据 schema 版本不匹配 */ METADATA_SCHEMA_VERSION_MISMATCH(1301), + /** 元数据 UUID 冲突 */ METADATA_UUID_CONFLICT(1302), // ... 其他常量🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java` around lines 3 - 15, 为公共枚举 VmMetadataErrors 添加类级别 Javadoc,简要说明该枚举表示 VM 元数据相关的错误码集合及使用场景;同时为每个枚举常量(如 METADATA_INVALID_FORMAT, METADATA_SCHEMA_VERSION_MISMATCH, METADATA_UUID_CONFLICT, METADATA_STORAGE_NOT_SUPPORTED, METADATA_CROSS_STORAGE_FORBIDDEN, METADATA_INSTALL_PATH_NOT_FOUND, METADATA_CACHE_VM_NOT_REGISTERABLE, METADATA_VM_REGISTERING, METADATA_READ_CORRUPTED, METADATA_PAYLOAD_TOO_LARGE, METADATA_PS_UNREACHABLE, METADATA_FEATURE_DISABLED)添加单独的 Javadoc 注释,描述错误含义、触发条件和可能的处理建议(简短一两句),使用标准 Javadoc 注释格式 (/** ... */) 放在枚举声明和各常量前以便生成 API 文档。header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java (1)
3-7: 考虑为vo字段添加注释说明其含义。字段
vo命名较为简短,虽然在 ZStack 代码库中 VO 通常表示 "Value Object",但添加简要注释可提高代码可读性,便于其他开发者理解其用途。📝 建议添加字段注释
public class ResourceMetadata { private String resourceUuid; + /** 资源对应的 VO (Value Object) 序列化数据 */ private String vo; + /** 资源关联的系统标签 JSON 数据 */ private String systemTags; + /** 资源配置 JSON 数据 */ private String resourceConfigs;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java` around lines 3 - 7, Add a brief Javadoc/comment for the vo field in class ResourceMetadata explaining what "vo" stands for and its intended semantic (e.g., "Value Object representing ..."), so callers and maintainers understand its purpose; update the declaration of the vo field in ResourceMetadata to include that comment (use the class name ResourceMetadata and field name vo to locate it).header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java (1)
5-14: 字段命名与相关类存在轻微不一致。此类使用
metadata字段名,而ReadVmInstanceMetadataReply使用vmMetadata。虽然不影响功能,但为了代码一致性和可维护性,建议统一命名风格。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java` around lines 5 - 14, GetVmInstanceMetadataFromPrimaryStorageReply uses the field and accessors named metadata (field metadata, methods getMetadata/setMetadata) which is inconsistent with ReadVmInstanceMetadataReply's vmMetadata; rename the field to vmMetadata and update the accessors to getVmMetadata/setVmMetadata (or getVmMetadata/setVmMetadata to match exact style in ReadVmInstanceMetadataReply), and update all call sites and serialization usages that reference metadata to the new vmMetadata names so the classes are consistent.sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java (1)
79-85: Boolean getter 命名建议Java Bean 规范中,
boolean类型字段的 getter 方法通常使用is前缀而非get前缀。♻️ 建议的修改
- public boolean getIncomplete() { + public boolean isIncomplete() { return this.incomplete; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java` around lines 79 - 85, The boolean getter for the field "incomplete" in class VmMetadataScanEntry should follow JavaBean conventions: replace the current getIncomplete() with an isIncomplete() method (or add isIncomplete() and mark getIncomplete() as `@Deprecated/forwarding` to isIncomplete() to preserve backward compatibility); update or add Javadoc accordingly and ensure any serializers/reflection usages reference the new isIncomplete() accessor while leaving setIncomplete(boolean) unchanged.conf/db/upgrade/V5.0.0__schema.sql (1)
9-10: 建议为时间列显式设置DEFAULT CURRENT_TIMESTAMP。当前写法依赖数据库隐式行为,跨 MySQL/MariaDB 配置时可读性与一致性较弱,建议显式声明默认值。
建议修改
- `lastOpDate` timestamp on update CURRENT_TIMESTAMP, - `createDate` timestamp, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,As per coding guidelines "Do not use
DEFAULT 0000-00-00 00:00:00, useDEFAULT CURRENT_TIMESTAMPinstead".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@conf/db/upgrade/V5.0.0__schema.sql` around lines 9 - 10, Update the timestamp column definitions to declare explicit defaults: change the `lastOpDate` column (currently `timestamp on update CURRENT_TIMESTAMP`) to include DEFAULT CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP, and set `createDate` (currently `timestamp`) to DEFAULT CURRENT_TIMESTAMP; modify the column definitions for `lastOpDate` and `createDate` accordingly so they use DEFAULT CURRENT_TIMESTAMP instead of relying on implicit behavior.header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java (1)
15-25: 建议为extractOldPrefix补充语义注释或单测示例。当前返回值是按 marker 截断得到“前缀”而非包含 marker,本身可行,但语义不够直观;补充注释/示例能明显降低后续误用风险。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java` around lines 15 - 25, Add a clear JavaDoc to VmInstanceMetadataConstants.extractOldPrefix explaining its exact behavior (inputs handled: null or not starting with "/", how it searches STORAGE_PATH_MARKERS, and that it returns the substring up to idx+1 when a marker is found or null otherwise) and add unit tests for extractOldPrefix that cover null input, paths not starting with "/", paths with a marker in the middle, markers at start/end, and multiple markers to show the chosen cut point; name tests descriptively (e.g., extractOldPrefix_nullInput_returnsNull, extractOldPrefix_noLeadingSlash_returnsNull, extractOldPrefix_markerInMiddle_returnsExpectedPrefix) so future readers can find intent and avoid misuse.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)
7-10: 请给这个 SPI 补上 Javadoc。这是跨主存储后端的扩展点,
getPrimaryStorageType()和calculatePathReplacements(...)的输入/输出约束现在只能靠调用方猜,后续实现很容易出现行为不一致。As per coding guidelines "接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java` around lines 7 - 10, 为 VmMetadataPathReplacementExtensionPoint 接口补充完整 Javadoc:在接口注释中说明这是用于跨主存储后端计算 VM 元数据路径替换的扩展点,并描述调用语义;为 getPrimaryStorageType() 添加注释说明返回值是该扩展点适用的主存储类型(非空、大小写约定等);为 calculatePathReplacements(String targetPsUuid, List<String> allOldPaths) 添加注释说明各参数含义(targetPsUuid 为目标主存储 UUID,allOldPaths 为需替换的原始路径列表)、输入约束(可否为 null 或空、是否可变)、返回值语义(PathReplacementResult 表示替换结果、何时返回空或空列表、是否保留顺序)以及线程/并发和异常行为;为内部静态类 PathReplacementResult 补充注释,说明其字段含义、不变性和使用场景。涉及符号:VmMetadataPathReplacementExtensionPoint、getPrimaryStorageType、calculatePathReplacements、PathReplacementResult。compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
37-37: 建议使用泛型类型参数。
Map data使用了原始类型,建议改为Map<String, Object> data以提高类型安全性。🤖 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/VmExpungeMetadataFlow.java` at line 37, The run method in VmExpungeMetadataFlow currently uses a raw Map parameter; change the signature of public void run(FlowTrigger trigger, Map data) to use generics: public void run(FlowTrigger trigger, Map<String, Object> data), update any internal usages/casts accordingly (e.g., references to data.get(...) should assume Object or cast to expected types), and add necessary imports if missing to ensure type-safety across the method.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
944-952: 冗余的全限定类名。在类内部定义的嵌套类中,使用
LocalStorageKvmBackend.AgentCommand和LocalStorageKvmBackend.AgentResponse是冗余的,可以直接使用AgentCommand和AgentResponse。♻️ 建议的修改
- public static class PrefixRebaseBackingFilesCmd extends LocalStorageKvmBackend.AgentCommand { + public static class PrefixRebaseBackingFilesCmd extends AgentCommand { public List<String> filePaths; public String oldPrefix; public String newPrefix; } - public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.AgentResponse { + public static class PrefixRebaseBackingFilesRsp extends AgentResponse { public int rebasedCount; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 944 - 952, The nested classes PrefixRebaseBackingFilesCmd and PrefixRebaseBackingFilesRsp use redundant fully-qualified types LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse; change their declarations to extend/declare AgentCommand and AgentResponse directly (i.e., replace LocalStorageKvmBackend.AgentCommand with AgentCommand and LocalStorageKvmBackend.AgentResponse with AgentResponse) so the nested classes read as public static class PrefixRebaseBackingFilesCmd extends AgentCommand { ... } and public static class PrefixRebaseBackingFilesRsp extends AgentResponse { ... }.
🤖 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/VmExpungeMetadataFlow.java`:
- Around line 89-93: VmExpungeMetadataFlow currently sets
cmsg.setHostUuid(hostUuid) even when hostUuid is null; update the flow to check
the resolved hostUuid (from spec.getVmInventory().getHostUuid() or
getLastHostUuid()) and if it is null do not set the host UUID and instead
short-circuit: log a warning and submit the GC/cleanup task directly or skip
sending the CleanupVmInstanceMetadataOnPrimaryStorageMsg to avoid confusing
handlers like LocalStorageBase.handle; locate the logic around
VmExpungeMetadataFlow, the spec.getVmInventory() usage, and the cmsg.setHostUuid
call and add the null check + fallback behavior there.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`:
- Around line 10-17: Update the REST path to include the vm instance UUID as a
path variable by adding an optional path with {uuid} to the `@RestRequest` on
class APIGetVmInstanceMetadataFromPrimaryStorageMsg; specifically, add
optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"} in the
`@RestRequest` annotation so the {uuid} placeholder maps to the existing private
String uuid field (annotated with `@APIParam`) and aligns with the project's
path-variable convention used by messages like
APIQueryVmInstanceResourceMetadataGroupMsg.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`:
- Around line 29-39: The interceptor handling
APIRegisterVmInstanceFromMetadataMsg must validate placement consistency: when
processing APIRegisterVmInstanceFromMetadataMsg (fields hostUuid, clusterUuid,
zoneUuid, primaryStorageUuid) fetch HostVO, ClusterVO, ZoneVO and
PrimaryStorage/PrimaryStorageClusterRef and enforce that the host's clusterUuid
equals the message.clusterUuid, the cluster's zoneUuid equals message.zoneUuid,
and that primaryStorageUuid is mounted to the cluster (exists a
PrimaryStorageClusterRefVO linking primaryStorageUuid to clusterUuid); if any
check fails throw an APIMessageInterceptionException with a clear error. Ensure
these checks are added into the existing interceptor entry point (e.g.,
intercept or validate method handling APIRegisterVmInstanceFromMetadataMsg) and
use the VO/Ref lookups used elsewhere in the codebase for consistency.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy`:
- Line 20: The empty desc string in
APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy should be filled with a concise
Chinese description matching the API's behavior; update the desc """""" in the
APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy file to a short sentence that
explains what APIUpdateVmInstanceMetadataMsg does (e.g., it updates VM instance
metadata, which keys are supported and whether it overwrites existing entries or
merges), ensuring the text is accurate and consistent with the implementation
and other language docs.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1989-1997: The call to
factory.getConnectedHostForOperation(getSelfInventory()) can throw
OperationFailureException (it is already handled elsewhere), so in each of the
four handlers (handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg), the Scan
handler, the Cleanup handler and the Rebase handler) you must stop treating the
API as "returns empty list" and instead wrap the
getConnectedHostForOperation(...) call in a try/catch for
OperationFailureException; on catching it construct the appropriate reply (e.g.
GetVmInstanceMetadataFromPrimaryStorageReply / corresponding Scan/Cleanup/Rebase
reply), set reply.setError(operr(e, "...")) or use e.getMessage() for context,
call bus.reply(msg, reply) and return — remove or ignore the subsequent
isEmpty() branch because it will not be reached when the API throws.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1866: The code in PrimaryStorageBase uses
Q.New(...).findValue() to get psType and then calls
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
and reports an error if ext==null, but findValue() can return null when the
primary storage was deleted; update the flow to first check the existence of the
PrimaryStorageVO result (e.g., assign the result of findValue() to psType and if
psType==null) and handle the deleted/absent primary storage case by setting an
appropriate error on evt (using evt.setError with a clear "primary storage not
found" message referencing msg.getPrimaryStorageUuid()) before calling
getExtensionFromMap, so getExtensionFromMap is never invoked with a null type
and the error becomes accurate instead of "type null does not support metadata".
- Around line 1790-1818: CleanupVmInstanceMetadataOnPrimaryStorageGC currently
sends CleanupVmInstanceMetadataOnPrimaryStorageMsg to every primary storage,
causing repeated "operation not supported" replies and GC retries; modify
CleanupVmInstanceMetadataOnPrimaryStorageGC.triggerNow to first filter target
primary storages by checking they are of a supported type and that a
VmMetadataPathBuildExtensionPoint implementation exists for them (reuse the same
check used in VmExpungeMetadataFlow), and only send
CleanupVmInstanceMetadataOnPrimaryStorageMsg to those primaries; alternatively,
skip scheduling GC for primaries that would fall back to PrimaryStorageBase
handlers (which return operr) so you avoid futile retries.
---
Outside diff comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 100-141: The message dispatch in
NfsPrimaryStorage.handleLocalMessage currently routes many message types but
misses four metadata local messages—UpdateVmInstanceMetadataOnPrimaryStorageMsg,
GetVmInstanceMetadataFromPrimaryStorageMsg,
ScanVmInstanceMetadataOnPrimaryStorageMsg,
CleanupVmInstanceMetadataOnPrimaryStorageMsg—so their handlers never run; update
handleLocalMessage to add else-if branches for each of these four message
classes and call the existing handle(...) methods for them (matching the pattern
used for RebaseVolumeBackingFileOnPrimaryStorageMsg and others) so those
messages are dispatched to their handlers instead of falling through to
super.handleLocalMessage(msg).
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Line 37: The run method in VmExpungeMetadataFlow currently uses a raw Map
parameter; change the signature of public void run(FlowTrigger trigger, Map
data) to use generics: public void run(FlowTrigger trigger, Map<String, Object>
data), update any internal usages/casts accordingly (e.g., references to
data.get(...) should assume Object or cast to expected types), and add necessary
imports if missing to ensure type-safety across the method.
In `@conf/db/upgrade/V5.0.0__schema.sql`:
- Around line 9-10: Update the timestamp column definitions to declare explicit
defaults: change the `lastOpDate` column (currently `timestamp on update
CURRENT_TIMESTAMP`) to include DEFAULT CURRENT_TIMESTAMP and ON UPDATE
CURRENT_TIMESTAMP, and set `createDate` (currently `timestamp`) to DEFAULT
CURRENT_TIMESTAMP; modify the column definitions for `lastOpDate` and
`createDate` accordingly so they use DEFAULT CURRENT_TIMESTAMP instead of
relying on implicit behavior.
In
`@header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java`:
- Around line 5-14: GetVmInstanceMetadataFromPrimaryStorageReply uses the field
and accessors named metadata (field metadata, methods getMetadata/setMetadata)
which is inconsistent with ReadVmInstanceMetadataReply's vmMetadata; rename the
field to vmMetadata and update the accessors to getVmMetadata/setVmMetadata (or
getVmMetadata/setVmMetadata to match exact style in
ReadVmInstanceMetadataReply), and update all call sites and serialization usages
that reference metadata to the new vmMetadata names so the classes are
consistent.
In
`@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java`:
- Around line 7-20: Rename the private field uuid in ReadVmInstanceMetadataMsg
to vmInstanceUuid and update the corresponding accessor methods: change
getUuid() to return vmInstanceUuid (or keep getUuid() as a delegating method if
needed for binary compatibility), change setUuid(String) to
setVmInstanceUuid(String) (or adapt setUuid to delegate), and update
getVmInstanceUuid() to return vmInstanceUuid; ensure all references inside
ReadVmInstanceMetadataMsg and any usages elsewhere are updated to the new
field/method names (getVmInstanceUuid, setVmInstanceUuid, vmInstanceUuid) so the
field name matches the interface method for clarity.
In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`:
- Around line 3-7: Add a brief Javadoc/comment for the vo field in class
ResourceMetadata explaining what "vo" stands for and its intended semantic
(e.g., "Value Object representing ..."), so callers and maintainers understand
its purpose; update the declaration of the vo field in ResourceMetadata to
include that comment (use the class name ResourceMetadata and field name vo to
locate it).
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java`:
- Around line 15-25: Add a clear JavaDoc to
VmInstanceMetadataConstants.extractOldPrefix explaining its exact behavior
(inputs handled: null or not starting with "/", how it searches
STORAGE_PATH_MARKERS, and that it returns the substring up to idx+1 when a
marker is found or null otherwise) and add unit tests for extractOldPrefix that
cover null input, paths not starting with "/", paths with a marker in the
middle, markers at start/end, and multiple markers to show the chosen cut point;
name tests descriptively (e.g., extractOldPrefix_nullInput_returnsNull,
extractOldPrefix_noLeadingSlash_returnsNull,
extractOldPrefix_markerInMiddle_returnsExpectedPrefix) so future readers can
find intent and avoid misuse.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java`:
- Line 18: 将 VmMetadataErrors 类中的私有字段 code 声明为 final,因为它仅在构造器中赋值且之后不再修改;在
VmMetadataErrors 的字段定义处将 "private String code;" 改为 "private final String code;"
并确保所有构造器对 code 的赋值保持不变(如果存在默认构造器需改为显式赋值或移除),以保证不可变性和线程安全性。
- Line 11: Rename the enum constant METADATA_VM_REGISTERING to a clearer error
name that expresses the blocking-by-registration semantics (e.g.,
METADATA_VM_REGISTRATION_IN_PROGRESS or
METADATA_OPERATION_BLOCKED_BY_REGISTRATION); update the enum declaration
(replace METADATA_VM_REGISTERING(1307) with the chosen identifier) and then
search & update all references/usages of METADATA_VM_REGISTERING across the
codebase (tests, logging, exception throws, switch statements) to use the new
constant name so compilation and behavior remain consistent.
- Around line 3-15: 为公共枚举 VmMetadataErrors 添加类级别 Javadoc,简要说明该枚举表示 VM
元数据相关的错误码集合及使用场景;同时为每个枚举常量(如 METADATA_INVALID_FORMAT,
METADATA_SCHEMA_VERSION_MISMATCH, METADATA_UUID_CONFLICT,
METADATA_STORAGE_NOT_SUPPORTED, METADATA_CROSS_STORAGE_FORBIDDEN,
METADATA_INSTALL_PATH_NOT_FOUND, METADATA_CACHE_VM_NOT_REGISTERABLE,
METADATA_VM_REGISTERING, METADATA_READ_CORRUPTED, METADATA_PAYLOAD_TOO_LARGE,
METADATA_PS_UNREACHABLE, METADATA_FEATURE_DISABLED)添加单独的 Javadoc
注释,描述错误含义、触发条件和可能的处理建议(简短一两句),使用标准 Javadoc 注释格式 (/** ... */) 放在枚举声明和各常量前以便生成 API
文档。
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 7-10: 为 VmMetadataPathReplacementExtensionPoint 接口补充完整
Javadoc:在接口注释中说明这是用于跨主存储后端计算 VM 元数据路径替换的扩展点,并描述调用语义;为 getPrimaryStorageType()
添加注释说明返回值是该扩展点适用的主存储类型(非空、大小写约定等);为 calculatePathReplacements(String
targetPsUuid, List<String> allOldPaths) 添加注释说明各参数含义(targetPsUuid 为目标主存储
UUID,allOldPaths 为需替换的原始路径列表)、输入约束(可否为 null 或空、是否可变)、返回值语义(PathReplacementResult
表示替换结果、何时返回空或空列表、是否保留顺序)以及线程/并发和异常行为;为内部静态类 PathReplacementResult
补充注释,说明其字段含义、不变性和使用场景。涉及符号:VmMetadataPathReplacementExtensionPoint、getPrimaryStorageType、calculatePathReplacements、PathReplacementResult。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 944-952: The nested classes PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp use redundant fully-qualified types
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse;
change their declarations to extend/declare AgentCommand and AgentResponse
directly (i.e., replace LocalStorageKvmBackend.AgentCommand with AgentCommand
and LocalStorageKvmBackend.AgentResponse with AgentResponse) so the nested
classes read as public static class PrefixRebaseBackingFilesCmd extends
AgentCommand { ... } and public static class PrefixRebaseBackingFilesRsp extends
AgentResponse { ... }.
In `@sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java`:
- Around line 79-85: The boolean getter for the field "incomplete" in class
VmMetadataScanEntry should follow JavaBean conventions: replace the current
getIncomplete() with an isIncomplete() method (or add isIncomplete() and mark
getIncomplete() as `@Deprecated/forwarding` to isIncomplete() to preserve backward
compatibility); update or add Javadoc accordingly and ensure any
serializers/reflection usages reference the new isIncomplete() accessor while
leaving setIncomplete(boolean) unchanged.
🪄 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: 8eb20e06-7aec-44ad-815b-7a98636003b5
⛔ Files ignored due to path filters (5)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/persistence.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xmlconf/serviceConfig/vmInstance.xmlis excluded by!**/*.xmlconf/springConfigXml/VmInstanceManager.xmlis excluded by!**/*.xml
📒 Files selected for processing (148)
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.javacompute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.javacompute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmSystemTags.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.javaheader/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.javaheader/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.javaheader/src/main/java/org/zstack/header/vm/VmInstanceState.javaheader/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.javaheader/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.javaheader/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.javaheader/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.javaheader/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.javaheader/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.javasdk/src/main/java/SourceClassMap.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovytestlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
| String hostUuid = spec.getVmInventory().getHostUuid(); | ||
| if (hostUuid == null) { | ||
| hostUuid = spec.getVmInventory().getLastHostUuid(); | ||
| } | ||
| cmsg.setHostUuid(hostUuid); |
There was a problem hiding this comment.
hostUuid 可能为 null,但仍会设置到消息中。
当 spec.getVmInventory().getHostUuid() 和 spec.getVmInventory().getLastHostUuid() 都为 null 时,cmsg.setHostUuid(null) 会被调用。这可能导致下游处理器(如 LocalStorageBase.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg))无法确定目标主机。
建议在发送消息前检查 hostUuid 是否为 null,如果为 null 则直接提交 GC 任务或记录日志并跳过。
🛠️ 建议的修复方案
String hostUuid = spec.getVmInventory().getHostUuid();
if (hostUuid == null) {
hostUuid = spec.getVmInventory().getLastHostUuid();
}
+ if (hostUuid == null) {
+ logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no host or lastHost, " +
+ "submitting GC job for retry", vmUuid));
+ submitGC(psUuid, vmUuid, rootVolumeUuid, metadataPath, null);
+ trigger.next();
+ return;
+ }
cmsg.setHostUuid(hostUuid);🤖 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/VmExpungeMetadataFlow.java`
around lines 89 - 93, VmExpungeMetadataFlow currently sets
cmsg.setHostUuid(hostUuid) even when hostUuid is null; update the flow to check
the resolved hostUuid (from spec.getVmInventory().getHostUuid() or
getLastHostUuid()) and if it is null do not set the host UUID and instead
short-circuit: log a warning and submit the GC/cleanup task directly or skip
sending the CleanupVmInstanceMetadataOnPrimaryStorageMsg to avoid confusing
handlers like LocalStorageBase.handle; locate the logic around
VmExpungeMetadataFlow, the spec.getVmInventory() usage, and the cmsg.setHostUuid
call and add the null check + fallback behavior there.
| @RestRequest( | ||
| path = "/primary-storage/vm-instances/metadata", | ||
| method = HttpMethod.GET, | ||
| responseClass = APIGetVmInstanceMetadataFromPrimaryStorageEvent.class | ||
| ) | ||
| public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APIMessage implements VmInstanceMessage { | ||
| @APIParam(resourceType = VmInstanceVO.class) | ||
| private String uuid; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查项目中类似 GET metadata API 的路径设计模式
rg -n '@RestRequest' --type=java -A 5 | rg -B 3 'metadata' | head -50Repository: MatheMatrix/zstack
Length of output: 5222
🏁 Script executed:
cat -n /dev/stdin << 'EOF' | head -40
header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
EOF
# First, let's see the full file
fd 'APIGetVmInstanceMetadataFromPrimaryStorageMsg.java' --type f -x cat -n {} | head -50Repository: MatheMatrix/zstack
Length of output: 1533
🏁 Script executed:
# Check the full content of the target file
find . -name 'APIGetVmInstanceMetadataFromPrimaryStorageMsg.java' -type f -exec cat -n {} \;Repository: MatheMatrix/zstack
Length of output: 1473
🏁 Script executed:
# Check APIScanVmInstanceMetadataFromPrimaryStorageMsg for comparison
find . -name 'APIScanVmInstanceMetadataFromPrimaryStorageMsg.java' -type f -exec cat -n {} \;Repository: MatheMatrix/zstack
Length of output: 1378
🏁 Script executed:
# Check one of the similar metadata query APIs that has optionalPaths
find . -name 'APIQueryVmInstanceResourceMetadataGroupMsg.java' -type f -exec cat -n {} \;Repository: MatheMatrix/zstack
Length of output: 1145
REST 路径设计需要调整
API 路径应包含 UUID 的路径变量。建议修改为支持 /primary-storage/vm-instances/{uuid}/metadata 的模式,与项目中类似的查询 API(如 APIQueryVmInstanceResourceMetadataGroupMsg)保持一致,并符合编码规范中"当 path 中引用消息类变量时,使用 {variableName} 格式"的要求。
可在 @RestRequest 中添加 optionalPaths 属性:
`@RestRequest`(
path = "/primary-storage/vm-instances/metadata",
optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"},
method = HttpMethod.GET,
responseClass = APIGetVmInstanceMetadataFromPrimaryStorageEvent.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/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`
around lines 10 - 17, Update the REST path to include the vm instance UUID as a
path variable by adding an optional path with {uuid} to the `@RestRequest` on
class APIGetVmInstanceMetadataFromPrimaryStorageMsg; specifically, add
optionalPaths = {"/primary-storage/vm-instances/{uuid}/metadata"} in the
`@RestRequest` annotation so the {uuid} placeholder maps to the existing private
String uuid field (annotated with `@APIParam`) and aligns with the project's
path-variable convention used by messages like
APIQueryVmInstanceResourceMetadataGroupMsg.
| @APIParam(resourceType = PrimaryStorageVO.class) | ||
| private String primaryStorageUuid; | ||
|
|
||
| @APIParam(resourceType = ZoneVO.class) | ||
| private String zoneUuid; | ||
|
|
||
| @APIParam(resourceType = ClusterVO.class) | ||
| private String clusterUuid; | ||
|
|
||
| @APIParam(resourceType = HostVO.class) | ||
| private String hostUuid; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== APIRegisterVmInstanceFromMetadataMsg validation in interceptor(s) ==='
rg -n -C4 --type=java \
'APIRegisterVmInstanceFromMetadataMsg|validateMetadataPath|primaryStorageUuid|zoneUuid|clusterUuid|hostUuid' \
compute header
echo
echo '=== Topology consistency checks around host/cluster/zone/primary storage ==='
rg -n -C4 --type=java \
'attached to cluster|belongs to cluster|belongs to zone|primaryStorageUuid|clusterUuid|zoneUuid|hostUuid' \
compute storage headerRepository: MatheMatrix/zstack
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== APIRegisterVmInstanceFromMetadataMsg class definition and inheritance ==="
fd -e java "APIRegisterVmInstanceFromMetadataMsg" header compute | \
xargs -I {} sh -c 'echo "File: {}"; head -50 "{}"' 2>/dev/null | head -100
echo
echo "=== Search for validation in VmInstanceApiInterceptor for this API ==="
fd -e java "VmInstanceApiInterceptor" compute | \
xargs -I {} sh -c 'grep -n "APIRegisterVmInstanceFromMetadataMsg\|validateMetadata\|validatePlacement" "{}" || true'
echo
echo "=== Search for NewVmInstanceMessage2 interface and its implementations ==="
rg -n "interface NewVmInstanceMessage2|class.*implements.*NewVmInstanceMessage2" --type=java -A 3 | head -80
echo
echo "=== Search for primaryStorageUuid validation with cluster/zone context ==="
rg -n "primaryStorageUuid.*cluster|cluster.*primaryStorageUuid|validatePrimaryStorage" --type=java -B2 -A2 | head -150Repository: MatheMatrix/zstack
Length of output: 24437
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Complete validate(APIRegisterVmInstanceFromMetadataMsg msg) method ==="
file="compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java"
if [ -f "$file" ]; then
wc -l "$file"
echo "---"
sed -n '1320,1380p' "$file"
fiRepository: MatheMatrix/zstack
Length of output: 1712
缺少对放置关系一致性的校验。
当前拦截器仅校验 UUID 存在性和元数据路径有效性,未验证 hostUuid、clusterUuid、zoneUuid 之间的关联关系,也未验证 primaryStorageUuid 是否已挂载到指定的 clusterUuid。这些校验应当在 API 拦截阶段明确执行,否则请求会在更深的注册流程中才失败,导致回滚困难。
建议在拦截器中补充:
hostUuid所属clusterUuid与消息提供的clusterUuid一致性检查clusterUuid所属zoneUuid与消息提供的zoneUuid一致性检查primaryStorageUuid已挂载到该clusterUuid的附加关系检查
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`
around lines 29 - 39, The interceptor handling
APIRegisterVmInstanceFromMetadataMsg must validate placement consistency: when
processing APIRegisterVmInstanceFromMetadataMsg (fields hostUuid, clusterUuid,
zoneUuid, primaryStorageUuid) fetch HostVO, ClusterVO, ZoneVO and
PrimaryStorage/PrimaryStorageClusterRef and enforce that the host's clusterUuid
equals the message.clusterUuid, the cluster's zoneUuid equals message.zoneUuid,
and that primaryStorageUuid is mounted to the cluster (exists a
PrimaryStorageClusterRefVO linking primaryStorageUuid to clusterUuid); if any
check fails throw an APIMessageInterceptionException with a clear error. Ensure
these checks are added into the existing interceptor entry point (e.g.,
intercept or validate method handling APIRegisterVmInstanceFromMetadataMsg) and
use the VO/Ref lookups used elsewhere in the codebase for consistency.
| protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { | ||
| GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); | ||
| List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); | ||
| if (connectedHosts.isEmpty()) { | ||
| reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); | ||
| bus.reply(msg, reply); | ||
| return; | ||
| } | ||
| String hostUuid = connectedHosts.get(0).getUuid(); |
There was a problem hiding this comment.
不要把 getConnectedHostForOperation() 当成“返回空列表”的 API。
同文件 Line 461 已经对这个调用做了 OperationFailureException 捕获,说明它在无可用主机时会直接抛异常。这里四个新 handler 都是先调用再判断 isEmpty(),你们准备的 reply.setError(...) 分支在无主机场景下很可能根本走不到。
🐛 建议修复
- List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
- if (connectedHosts.isEmpty()) {
+ List<HostInventory> connectedHosts;
+ try {
+ connectedHosts = factory.getConnectedHostForOperation(getSelfInventory());
+ } catch (OperationFailureException e) {
reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid()));
bus.reply(msg, reply);
return;
- }
+ }以上模式需要在 Get/Scan/Cleanup/Rebase 四个 handler 里一起套用。
Also applies to: 2016-2023, 2041-2048, 2066-2073
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`
around lines 1989 - 1997, The call to
factory.getConnectedHostForOperation(getSelfInventory()) can throw
OperationFailureException (it is already handled elsewhere), so in each of the
four handlers (handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg), the Scan
handler, the Cleanup handler and the Rebase handler) you must stop treating the
API as "returns empty list" and instead wrap the
getConnectedHostForOperation(...) call in a try/catch for
OperationFailureException; on catching it construct the appropriate reply (e.g.
GetVmInstanceMetadataFromPrimaryStorageReply / corresponding Scan/Cleanup/Rebase
reply), set reply.setError(operr(e, "...")) or use e.getMessage() for context,
call bus.reply(msg, reply) and return — remove or ignore the subsequent
isEmpty() branch because it will not be reached when the API throws.
| protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | ||
| reply.setError(operr("operation not supported")); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { | ||
| GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); | ||
| reply.setError(operr("operation not supported")); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { | ||
| ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); | ||
| reply.setError(operr("operation not supported")); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); | ||
| reply.setError(operr("operation not supported")); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { | ||
| RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); | ||
| reply.setError(operr("operation not supported")); | ||
| bus.reply(msg, reply); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== Metadata handlers implemented outside PrimaryStorageBase ==='
rg -n -C2 --type=java \
'handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|RebaseVolumeBackingFileOnPrimaryStorageMsg)\s+msg\)' \
storage plugin header
echo
echo '=== Metadata message call sites ==='
rg -n -C2 --type=java \
'new (UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|RebaseVolumeBackingFileOnPrimaryStorageMsg)\(' \
compute storage plugin headerRepository: MatheMatrix/zstack
Length of output: 9995
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== All PrimaryStorageBase subclasses ==='
rg -n --type=java 'class \w+\s+extends\s+(PrimaryStorageBase|LocalStorageBase)' \
plugin storage
echo
echo '=== LocalStorageBase handler overrides ==='
rg -n --type=java '@Override.*\n.*protected void handle' \
plugin/localstorage -A2 | head -80
echo
echo '=== CleanupVmInstanceMetadataOnPrimaryStorageGC filter logic ==='
cat -n plugin/compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java | head -60Repository: MatheMatrix/zstack
Length of output: 1574
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== VmExpungeMetadataFlow around line 83 ==='
cat -n compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java | sed -n '60,110p'
echo
echo '=== APIScanVmInstanceMetadataFromPrimaryStorageMsg implementation ==='
rg -n 'handle\(APIScanVmInstanceMetadataFromPrimaryStorageMsg' \
storage -A30 | head -100Repository: MatheMatrix/zstack
Length of output: 7216
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== CleanupVmInstanceMetadataOnPrimaryStorageGC.java ==='
cat -n compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
echo
echo '=== LocalStorageBase handler overrides (all methods) ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
plugin/localstorage --type=java
echo
echo '=== SMPPrimaryStorageBase metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
plugin/sharedMountPointPrimaryStorage --type=java
echo
echo '=== CephPrimaryStorageBase metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
plugin/ceph --type=java
echo
echo '=== ExternalPrimaryStorage metadata handlers ==='
rg -n 'protected void handle\((UpdateVmInstanceMetadataOnPrimaryStorageMsg|GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg)\s+msg\)' \
storage/src/main/java/org/zstack/storage/addon/primary --type=javaRepository: MatheMatrix/zstack
Length of output: 3265
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== Check all backends for metadata handler overrides ==='
for pattern in 'UpdateVmInstanceMetadataOnPrimaryStorageMsg' 'GetVmInstanceMetadataFromPrimaryStorageMsg' 'ScanVmInstanceMetadataFromPrimaryStorageMsg' 'CleanupVmInstanceMetadataOnPrimaryStorageMsg'; do
echo "=== Searching for $pattern ==="
rg -n "handle\($pattern" --type=java plugin storage 2>/dev/null || echo "Not found in: $pattern"
done
echo
echo '=== Check VmExpungeMetadataFlow failure handling ==='
cat -n compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java | sed -n '100,115p'Repository: MatheMatrix/zstack
Length of output: 7081
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '=== Verify NfsPrimaryStorage is the only base handler implementer ==='
rg -n 'class.*extends PrimaryStorageBase' --type=java plugin storage | head -20
echo
echo '=== Check if other backends explicitly support metadata ==='
rg -n '@Bean.*VmMetadataPathBuildExtensionPoint|class.*implements.*VmMetadataPathBuildExtensionPoint' \
--type=java plugin storage header 2>/dev/null | head -30Repository: MatheMatrix/zstack
Length of output: 1649
CleanupVmInstanceMetadataOnPrimaryStorageGC 需要增加存储类型检查,防止向不支持的后端重复发送消息。
当前代码存在两层风险:
-
VmExpungeMetadataFlow 虽然在初始发送时检查了 VmMetadataPathBuildExtensionPoint(仅 NFS 和 LocalStorage 实现),但失败时会提交 GC 任务重试(第 107 行)。
-
CleanupVmInstanceMetadataOnPrimaryStorageGC 则跳过了所有能力检查,直接向任何存在的主存储无条件发送清理消息(第 41-48 行)。结果:
- SMPPrimaryStorageBase、ExternalPrimaryStorage、CephPrimaryStorageBase 无对应实现 → 返回"operation not supported"
- LocalStorageBase 虽有扩展点但基类未覆盖消息处理 → 也返回默认错误
- 仅 NfsPrimaryStorage 完整实现所有 4 个处理器
这导致清理失败的存储类型会反复通过 GC 重试循环发送消息,产生持续的失败日志和重试开销。
建议 CleanupVmInstanceMetadataOnPrimaryStorageGC 在 triggerNow 方法中增加存储类型和扩展点的验证,模仿 VmExpungeMetadataFlow 的检查逻辑(第 65-71 行),或者确保所有使用此功能的存储后端都实现了对应的消息处理器。
🤖 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/primary/PrimaryStorageBase.java`
around lines 1790 - 1818, CleanupVmInstanceMetadataOnPrimaryStorageGC currently
sends CleanupVmInstanceMetadataOnPrimaryStorageMsg to every primary storage,
causing repeated "operation not supported" replies and GC retries; modify
CleanupVmInstanceMetadataOnPrimaryStorageGC.triggerNow to first filter target
primary storages by checking they are of a supported type and that a
VmMetadataPathBuildExtensionPoint implementation exists for them (reuse the same
check used in VmExpungeMetadataFlow), and only send
CleanupVmInstanceMetadataOnPrimaryStorageMsg to those primaries; alternatively,
skip scheduling GC for primaries that would fall back to PrimaryStorageBase
handlers (which return operr) so you avoid futile retries.
dfbb104 to
1182c2c
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (3)
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1863-1869:⚠️ Potential issue | 🟡 Minor主存储被删除时
psType为 null,错误消息将不准确。
findValue()在 API 路由后、实际执行前可能返回null(主存储被删除的竞态情况)。当前代码会将其误报为 "type null does not support metadata",且getExtensionFromMap(null, ...)的行为不可预期。🩹 建议修改
String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue(); + if (psType == null) { + evt.setError(Platform.operr("primary storage[uuid:%s] not found", msg.getPrimaryStorageUuid())); + bus.publish(evt); + return; + } VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); if (ext == null) { evt.setError(Platform.operr("primary storage type %s does not support metadata", psType));🤖 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/primary/PrimaryStorageBase.java` around lines 1863 - 1869, The code calls Q.New(...).select(...).findValue() into psType and then uses pluginRgty.getExtensionFromMap(psType,...), but psType can be null if the primary storage was deleted; add a null check on psType (the result of Q.New(...).select(...).findValue()) before calling pluginRgty.getExtensionFromMap and return a clear not-found error (e.g. set evt.setError with an operation error indicating the primary storage UUID was not found or deleted) and publish evt, instead of proceeding to call getExtensionFromMap with a null type or logging "type null does not support metadata".compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
89-93:⚠️ Potential issue | 🔴 Critical
hostUuid为空时不要继续下发 cleanup。这里会把
null直接塞进CleanupVmInstanceMetadataOnPrimaryStorageMsg。对 LocalStorage 来说,下游只能靠hostUuid或 root volume ref 反解宿主机;而VmExpungeMetadataFlow在conf/springConfigXml/VmInstanceManager.xml:116-122的 expunge 链里排在 root/memory/cache volume flow 之后,root volume ref 此时可能已经被前面的 flow 删掉了。结果就是首轮 cleanup 失败,随后提交的 GC 仍然带着同样的null hostUuid,会一直重试但无法收敛。这里至少要在hostUuid == null时直接短路,或改成一个不依赖 host 的兜底路径。🛠️ 一个最小修复方向
String hostUuid = spec.getVmInventory().getHostUuid(); if (hostUuid == null) { hostUuid = spec.getVmInventory().getLastHostUuid(); } +if (hostUuid == null) { + logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no hostUuid/lastHostUuid, skip metadata cleanup", vmUuid)); + trigger.next(); + return; +} cmsg.setHostUuid(hostUuid);🤖 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/VmExpungeMetadataFlow.java` around lines 89 - 93, The flow VmExpungeMetadataFlow currently sets hostUuid on CleanupVmInstanceMetadataOnPrimaryStorageMsg using spec.getVmInventory().getHostUuid() (falling back to getLastHostUuid()), which can be null and causes cleanup to be repeatedly retried; change the logic in VmExpungeMetadataFlow so that before calling cmsg.setHostUuid(...) you check if hostUuid is null and short-circuit (skip sending the cleanup message) or use a host-independent fallback path; specifically, locate the block that reads hostUuid from spec.getVmInventory() and either return/complete the flow early when hostUuid == null or construct an alternative cleanup flow that does not rely on hostUuid instead of putting a null into cmsg.setHostUuid.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1991-1996:⚠️ Potential issue | 🟠 Major
getConnectedHostForOperation()的异常路径这里还没接住。同文件 Line 461-470 已经按“可能直接抛
OperationFailureException”来处理了。这里 Line 1991、Line 2016、Line 2041、Line 2066 继续先拿列表再判空,会让reply.setError(...)分支在无可用主机时根本走不到。请像现有handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg)一样先 catch 再构造对应 reply。🐛 建议修复模式
- List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); - if (connectedHosts.isEmpty()) { + List<HostInventory> connectedHosts; + try { + connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + } catch (OperationFailureException e) { + reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + if (connectedHosts.isEmpty()) { reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); bus.reply(msg, reply); return; }Also applies to: 2016-2021, 2041-2046, 2066-2071
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java` around lines 1991 - 1996, The calls to factory.getConnectedHostForOperation(getSelfInventory()) in NfsPrimaryStorage can throw an OperationFailureException and must be wrapped in a try/catch so the code constructs and returns the appropriate reply on exception (same pattern used in handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg)); for each occurrence (the blocks that currently do List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(...); if (connectedHosts.isEmpty()) { reply.setError(...); bus.reply(msg, reply); return; }) change to try { connectedHosts = factory.getConnectedHostForOperation(...) } catch (OperationFailureException e) { reply.setError(errf.stringToOperationError(e.getMessage())); bus.reply(msg, reply); return; } and keep the existing empty-list check afterwards so the reply variable and bus.reply(msg, reply) are always used on error paths.
🧹 Nitpick comments (2)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)
80-89: 代码重复:建议复用resolveBaseDir方法。此处的 baseDir 解析逻辑与
resolveBaseDir方法(lines 29-38)几乎相同,唯一区别是错误处理方式不同(warn + 返回空结果 vs 抛出异常)。建议提取一个可复用的私有方法,或者直接调用resolveBaseDir并在外层捕获异常处理。♻️ 建议重构
`@Override` public PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths) { if (allOldPaths == null || allOldPaths.isEmpty()) { PathReplacementResult result = new PathReplacementResult(); result.setMetadataToCurrentPathMap(Collections.emptyMap()); return result; } - String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); - if (baseDir == null) { - baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); - } - if (baseDir == null) { + String baseDir; + try { + baseDir = resolveBaseDir(targetPsUuid); + } catch (CloudRuntimeException e) { logger.warn(String.format("NFS PS[uuid:%s] has no mountPath or url, path replacement disabled", targetPsUuid)); PathReplacementResult result = new PathReplacementResult(); result.setMetadataToCurrentPathMap(Collections.emptyMap()); return result; } - String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/"; + String newPrefix = baseDir + "/";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java` around lines 80 - 89, The baseDir resolution in NfsVmMetadataExtension (the block that queries PrimaryStorageVO.select(mountPath/url) for targetPsUuid) duplicates logic from the resolveBaseDir(...) method; refactor to reuse resolveBaseDir by calling resolveBaseDir(targetPsUuid) and handle its exception here: catch the exception thrown by resolveBaseDir and perform the current warn+return empty PathReplacementResult behavior, or extract a private helper used by both places that returns Optional<String>/null and let callers map to their respective error handling; update references in the PathReplacementResult-returning code path to use this shared helper instead of duplicating the DB lookup.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2157-2160:hostUuid在扫描结果里更像“scanner host”,不要和资源归属语义混用。Line 2159 把每个 entry 的
hostUuid都回填成当前扫描宿主机。NFS 目录是共享的,换一台 connected host 再扫,这个值就会跟着变;如果上层把它当成元数据条目的所属/来源 host,结果会不稳定。建议至少把语义文档化,或拆成单独字段。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java` around lines 2157 - 2160, The scan currently overwrites each metadata entry's hostUuid via entry.setHostUuid(hostUuid) (in the ScanVmInstanceMetadataFromPrimaryStorageReply population), which conflates the scanning host with the metadata's owner/source; instead, stop mutating the entry.hostUuid and add a separate scanner field (e.g. scannerHostUuid) on the metadata entry or on ScanVmInstanceMetadataFromPrimaryStorageReply, populate that with the current hostUuid, and document the semantics so callers can distinguish the scanner host from the resource owner; update references to rsp.metadataEntries, entry.setHostUuid(hostUuid), and r.setVmInstanceMetadata(...) accordingly.
🤖 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/VmInstanceState.java`:
- Around line 194-198: 在 Registering
状态的事务注册里(Registering.Registering.transactions / Transaction 调用序列),为探测/对账类兜底事件
VmInstanceStateEvent.unknown 和 VmInstanceStateEvent.noState 补上映射(指向现有的离线容错状态
VmInstanceState.Stopped,与已注册的 stopped/destroying/destroyed 一致),以避免 nextState()
在接收到 unknown/noState 时抛出 CloudRuntimeException;也请保持映射风格和其它 Transaction
条目一致以便行为统一。
- Line 34: The Registering enum constant in VmInstanceState is currently
constructed with a null drivenEvent which causes downstream NPEs when consumers
like VmInstanceBase.changeVmStateInDb(...) access .n without null checks; change
the Registering declaration to supply a valid non-null drivenEvent (use an
appropriate existing VmInstanceStateDrivenEvent value) so all state transitions
have a concrete event, or alternatively add null-safe handling in
changeVmStateInDb and other consumers to guard against a null drivenEvent;
prefer setting a non-null event on Registering in VmInstanceState to keep
semantics consistent.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3471-3474: The loop using While<>(connectedHostUuids).all
currently calls getHypervisorBackendFactoryByHostUuid(...) and
f.getHypervisorBackend(self) outside of any per-host try/catch, so if those
calls throw (e.g., host unbound) the completion (com) is never resolved; wrap
the per-host resolution and bkd.handle(...) invocation in a try/catch inside the
lambda (the body passed to While<>(...).all), catch any Throwable, call
com.addError(...) with context (hostUuid and exception) and then com.done() (or
otherwise mark that host as failed) so the loop continues to the next host;
reference getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend, and bkd.handle in your changes.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3923-3940: The
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid,
ReturnValueCompletion) method calls httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,
...) with hostUuid possibly null; add an early-null check for hostUuid at the
start of that method and short-circuit the logic (e.g., skip the httpCall and
either call completion.success(new
CleanupVmInstanceMetadataOnPrimaryStorageReply()) or trigger the GC/cleanup
scheduling path used for host-less local storage) so the method returns cleanly
when hostUuid is null; ensure the short-circuit uses the same completion
semantics as the success branch and log a debug/info message indicating hostUuid
was missing.
- Around line 3942-3949: handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)
currently forwards installPaths/oldPrefix/newPrefix to
PrefixRebaseBackingFilesCmd without validation; add a fail‑fast validation in
the handle method before constructing/sending PrefixRebaseBackingFilesCmd:
verify msg.getInstallPaths() is non‑null and non‑empty, each path is a safe
absolute/storage path and matches expected backing file pattern, and
oldPrefix/newPrefix are non‑null, non‑empty and do not contain path traversal or
illegal characters; on validation failure, complete via the provided completion
with an error (use ReturnValueCompletion.completeExceptionally or the existing
completion.fail pattern) rather than calling httpCall, so
PrefixRebaseBackingFilesCmd and httpCall are only used when inputs are
validated.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 92-97: The current extractOldPrefix logic
(VmInstanceMetadataConstants.extractOldPrefix) wrongly matches markers inside
the mount path; update it to perform a boundary-aware search (e.g., use
lastIndexOf(marker) and verify the match corresponds to the path segment
boundary, or change the signature to accept the mount root and only search after
that boundary) so it returns the correct prefix ending at the mount path (e.g.,
"/nfs/dataVolumes/backup/"); then ensure callers such as the loop that builds
oldPrefix and calculatePathReplacements use the revised method/signature so
replacements are computed from the correct oldPrefix.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-93: The flow VmExpungeMetadataFlow currently sets hostUuid on
CleanupVmInstanceMetadataOnPrimaryStorageMsg using
spec.getVmInventory().getHostUuid() (falling back to getLastHostUuid()), which
can be null and causes cleanup to be repeatedly retried; change the logic in
VmExpungeMetadataFlow so that before calling cmsg.setHostUuid(...) you check if
hostUuid is null and short-circuit (skip sending the cleanup message) or use a
host-independent fallback path; specifically, locate the block that reads
hostUuid from spec.getVmInventory() and either return/complete the flow early
when hostUuid == null or construct an alternative cleanup flow that does not
rely on hostUuid instead of putting a null into cmsg.setHostUuid.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1991-1996: The calls to
factory.getConnectedHostForOperation(getSelfInventory()) in NfsPrimaryStorage
can throw an OperationFailureException and must be wrapped in a try/catch so the
code constructs and returns the appropriate reply on exception (same pattern
used in handle(RevertVolumeFromSnapshotOnPrimaryStorageMsg)); for each
occurrence (the blocks that currently do List<HostInventory> connectedHosts =
factory.getConnectedHostForOperation(...); if (connectedHosts.isEmpty()) {
reply.setError(...); bus.reply(msg, reply); return; }) change to try {
connectedHosts = factory.getConnectedHostForOperation(...) } catch
(OperationFailureException e) {
reply.setError(errf.stringToOperationError(e.getMessage())); bus.reply(msg,
reply); return; } and keep the existing empty-list check afterwards so the reply
variable and bus.reply(msg, reply) are always used on error paths.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1869: The code calls Q.New(...).select(...).findValue() into
psType and then uses pluginRgty.getExtensionFromMap(psType,...), but psType can
be null if the primary storage was deleted; add a null check on psType (the
result of Q.New(...).select(...).findValue()) before calling
pluginRgty.getExtensionFromMap and return a clear not-found error (e.g. set
evt.setError with an operation error indicating the primary storage UUID was not
found or deleted) and publish evt, instead of proceeding to call
getExtensionFromMap with a null type or logging "type null does not support
metadata".
---
Nitpick comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2157-2160: The scan currently overwrites each metadata entry's
hostUuid via entry.setHostUuid(hostUuid) (in the
ScanVmInstanceMetadataFromPrimaryStorageReply population), which conflates the
scanning host with the metadata's owner/source; instead, stop mutating the
entry.hostUuid and add a separate scanner field (e.g. scannerHostUuid) on the
metadata entry or on ScanVmInstanceMetadataFromPrimaryStorageReply, populate
that with the current hostUuid, and document the semantics so callers can
distinguish the scanner host from the resource owner; update references to
rsp.metadataEntries, entry.setHostUuid(hostUuid), and
r.setVmInstanceMetadata(...) accordingly.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 80-89: The baseDir resolution in NfsVmMetadataExtension (the block
that queries PrimaryStorageVO.select(mountPath/url) for targetPsUuid) duplicates
logic from the resolveBaseDir(...) method; refactor to reuse resolveBaseDir by
calling resolveBaseDir(targetPsUuid) and handle its exception here: catch the
exception thrown by resolveBaseDir and perform the current warn+return empty
PathReplacementResult behavior, or extract a private helper used by both places
that returns Optional<String>/null and let callers map to their respective error
handling; update references in the PathReplacementResult-returning code path to
use this shared helper instead of duplicating the DB lookup.
🪄 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: ef2262b9-d6b9-4ac1-80e3-0df96d884ac4
⛔ Files ignored due to path filters (5)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/persistence.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xmlconf/serviceConfig/vmInstance.xmlis excluded by!**/*.xmlconf/springConfigXml/VmInstanceManager.xmlis excluded by!**/*.xml
📒 Files selected for processing (148)
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.javacompute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.javacompute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmSystemTags.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.javaheader/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.javaheader/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.javaheader/src/main/java/org/zstack/header/vm/VmInstanceState.javaheader/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.javaheader/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.javaheader/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.javaheader/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.javaheader/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.javaheader/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.javasdk/src/main/java/SourceClassMap.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovytestlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
✅ Files skipped from review due to trivial changes (89)
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- sdk/src/main/java/SourceClassMap.java
🚧 Files skipped from review as they are similar to previous changes (42)
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
| Unknown(VmInstanceStateEvent.unknown), | ||
| Crashed(VmInstanceStateEvent.crashed); | ||
| Crashed(VmInstanceStateEvent.crashed), | ||
| Registering(null); |
There was a problem hiding this comment.
避免为新状态使用空驱动事件(会触发下游 NPE)
Line 34 把 Registering 的 drivenEvent 设为 null。结合 compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java:1,50 中对状态驱动事件的无空保护访问(changeVmStateInDb(... .n)),这里会在注册态流转/落库时触发空指针并中断流程。建议为 Registering 提供非空事件,或在所有消费点统一做空值保护。
🤖 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/VmInstanceState.java` at line 34,
The Registering enum constant in VmInstanceState is currently constructed with a
null drivenEvent which causes downstream NPEs when consumers like
VmInstanceBase.changeVmStateInDb(...) access .n without null checks; change the
Registering declaration to supply a valid non-null drivenEvent (use an
appropriate existing VmInstanceStateDrivenEvent value) so all state transitions
have a concrete event, or alternatively add null-safe handling in
changeVmStateInDb and other consumers to guard against a null drivenEvent;
prefer setting a non-null event on Registering in VmInstanceState to keep
semantics consistent.
| new While<>(connectedHostUuids).all((hostUuid, com) -> { | ||
| LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); | ||
| LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); | ||
| bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) { |
There was a problem hiding this comment.
把 backend 解析也纳入逐主机的失败分支。
这里先拿到 connectedHostUuids,随后又在 Line 3472-3473 重新按 hostUuid 查 factory/backend。主机如果在这两个步骤之间被解绑或删除,getHypervisorBackendFactoryByHostUuid() 会在 bkd.handle() 之前直接抛异常,当前分支既不会 com.addError() 也不会 com.done(),一次主机抖动就可能把本来设计成“部分失败继续”的扫描整体打断。建议把 factory/backend 解析包进 try/catch,按单主机失败记账后继续扫描。
🛠️ 一个最小修复方向
new While<>(connectedHostUuids).all((hostUuid, com) -> {
- LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
- LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self);
+ final LocalStorageHypervisorBackend bkd;
+ try {
+ LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid);
+ bkd = f.getHypervisorBackend(self);
+ } catch (Exception e) {
+ com.addError(operr("failed to prepare vm metadata scan on host[uuid:%s]: %s", hostUuid, e.getMessage()));
+ com.done();
+ return;
+ }
bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`
around lines 3471 - 3474, The loop using While<>(connectedHostUuids).all
currently calls getHypervisorBackendFactoryByHostUuid(...) and
f.getHypervisorBackend(self) outside of any per-host try/catch, so if those
calls throw (e.g., host unbound) the completion (com) is never resolved; wrap
the per-host resolution and bkd.handle(...) invocation in a try/catch inside the
lambda (the body passed to While<>(...).all), catch any Throwable, call
com.addError(...) with context (hostUuid and exception) and then com.done() (or
otherwise mark that host as failed) so the loop continues to the next host;
reference getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend, and bkd.handle in your changes.
| @Override | ||
| void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { | ||
| CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); | ||
| cmd.metadataPath = msg.getMetadataPath(); | ||
|
|
||
| httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) { | ||
| @Override | ||
| public void success(CleanupVmMetadataRsp rsp) { | ||
| CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); | ||
| completion.success(reply); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| completion.fail(errorCode); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
缺少 hostUuid 空值保护会导致清理链路失败
Line 3928 直接用 hostUuid 发起 httpCall。在本地存储场景中,hostUuid 为空时该调用会失败,导致元数据清理无法完成。建议在入口先做空值短路(跳过或转 GC)。
建议修复
`@Override`
void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+ if (StringUtils.isBlank(hostUuid)) {
+ logger.warn(String.format("skip cleanup vm metadata on local storage[uuid:%s], metadataPath:%s because hostUuid is null/blank",
+ self.getUuid(), msg.getMetadataPath()));
+ completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply());
+ return;
+ }
+
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.metadataPath = msg.getMetadataPath();As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3923 - 3940, The
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid,
ReturnValueCompletion) method calls httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,
...) with hostUuid possibly null; add an early-null check for hostUuid at the
start of that method and short-circuit the logic (e.g., skip the httpCall and
either call completion.success(new
CleanupVmInstanceMetadataOnPrimaryStorageReply()) or trigger the GC/cleanup
scheduling path used for host-less local storage) so the method returns cleanly
when hostUuid is null; ensure the short-circuit uses the same completion
semantics as the success branch and log a debug/info message indicating hostUuid
was missing.
| @Override | ||
| void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<RebaseVolumeBackingFileOnPrimaryStorageReply> completion) { | ||
| PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd(); | ||
| cmd.filePaths = msg.getInstallPaths(); | ||
| cmd.oldPrefix = msg.getOldPrefix(); | ||
| cmd.newPrefix = msg.getNewPrefix(); | ||
|
|
||
| httpCall(PREFIX_REBASE_BACKING_FILES_PATH, hostUuid, cmd, PrefixRebaseBackingFilesRsp.class, new ReturnValueCompletion<PrefixRebaseBackingFilesRsp>(completion) { |
There was a problem hiding this comment.
前缀重写操作缺少入参校验,存在误改 backing chain 风险
当前直接透传 installPaths/oldPrefix/newPrefix 到 agent。若前缀或路径输入异常,会把错误映射下发,风险较高。建议在本地先做 fail-fast 校验。
建议修复
`@Override`
void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<RebaseVolumeBackingFileOnPrimaryStorageReply> completion) {
+ if (CollectionUtils.isEmpty(msg.getInstallPaths())
+ || StringUtils.isBlank(msg.getOldPrefix())
+ || StringUtils.isBlank(msg.getNewPrefix())) {
+ completion.fail(operr("invalid rebase request: installPaths/oldPrefix/newPrefix must not be empty"));
+ return;
+ }
+
+ Optional<String> illegalPath = msg.getInstallPaths().stream()
+ .filter(p -> p == null || !p.startsWith(msg.getOldPrefix()))
+ .findFirst();
+ if (illegalPath.isPresent()) {
+ completion.fail(operr("invalid rebase request: installPath[%s] does not start with oldPrefix[%s]",
+ illegalPath.get(), msg.getOldPrefix()));
+ return;
+ }
+
PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd();
cmd.filePaths = msg.getInstallPaths();
cmd.oldPrefix = msg.getOldPrefix();As per coding guidelines "条件判断中的处理逻辑提前结束或抽成独立方法".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 3942 - 3949, handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)
currently forwards installPaths/oldPrefix/newPrefix to
PrefixRebaseBackingFilesCmd without validation; add a fail‑fast validation in
the handle method before constructing/sending PrefixRebaseBackingFilesCmd:
verify msg.getInstallPaths() is non‑null and non‑empty, each path is a safe
absolute/storage path and matches expected backing file pattern, and
oldPrefix/newPrefix are non‑null, non‑empty and do not contain path traversal or
illegal characters; on validation failure, complete via the provided completion
with an error (use ReturnValueCompletion.completeExceptionally or the existing
completion.fail pattern) rather than calling httpCall, so
PrefixRebaseBackingFilesCmd and httpCall are only used when inputs are
validated.
| // Extract old prefix from the first recognizable path | ||
| String oldPrefix = null; | ||
| for (String path : allOldPaths) { | ||
| oldPrefix = VmInstanceMetadataConstants.extractOldPrefix(path); | ||
| if (oldPrefix != null) break; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查看 extractOldPrefix 的实现逻辑
ast-grep --pattern 'public static String extractOldPrefix($$$) {
$$$
}'Repository: MatheMatrix/zstack
Length of output: 1477
🏁 Script executed:
#!/bin/bash
# Find the STORAGE_PATH_MARKERS definition in VmInstanceMetadataConstants
rg -A 10 'STORAGE_PATH_MARKERS' header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaRepository: MatheMatrix/zstack
Length of output: 580
🏁 Script executed:
#!/bin/bash
# View the full calculatePathReplacements method for context
sed -n '72,116p' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.javaRepository: MatheMatrix/zstack
Length of output: 2199
extractOldPrefix 使用简单的字符串匹配,当挂载路径本身包含标记时可能导致错误的前缀提取。
VmInstanceMetadataConstants.extractOldPrefix() 使用 path.indexOf(marker) 查找硬编码的路径标记(/rootVolumes/、/dataVolumes/、/volumeSnapshots/、/memory/),并返回第一个匹配位置之前的子串。如果挂载路径本身包含这些标记,例如挂载路径为 /nfs/dataVolumes/backup 而旧路径为 /nfs/dataVolumes/backup/dataVolumes/vm1/vol.qcow2,则 indexOf("/dataVolumes/") 会匹配挂载路径部分(位置 5),导致返回 / 而非正确的 /nfs/dataVolumes/backup/。这会导致 calculatePathReplacements 中的路径映射产生错误的 old→new 转换,破坏卷的 backing file 链。
建议:改进匹配策略,例如从挂载路径末尾开始扫描,或显式传递挂载路径边界信息。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`
around lines 92 - 97, The current extractOldPrefix logic
(VmInstanceMetadataConstants.extractOldPrefix) wrongly matches markers inside
the mount path; update it to perform a boundary-aware search (e.g., use
lastIndexOf(marker) and verify the match corresponds to the path segment
boundary, or change the signature to accept the mount root and only search after
that boundary) so it returns the correct prefix ending at the mount path (e.g.,
"/nfs/dataVolumes/backup/"); then ensure callers such as the loop that builds
oldPrefix and calculatePathReplacements use the revised method/signature so
replacements are computed from the correct oldPrefix.
1182c2c to
295ef96
Compare
APIImpact Resolves: ZSV-11559 Change-Id: I6b6a6378627264646d6a76726762736e77787373
295ef96 to
520245e
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (2)
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
89-93:⚠️ Potential issue | 🟠 Major不要把
null hostUuid继续下发到主存清理消息。从未启动过的 VM 上,
hostUuid和lastHostUuid都可能为空;Line 93 仍然把它塞进cmsg。而这个 flow 又是在根卷 expunge 之后执行,LocalStorage 一侧通常已经无法再靠rootVolumeUuid反查 host,结果是清理/GC 很容易一直失败。🛠️ 一个更安全的短路方式
String hostUuid = spec.getVmInventory().getHostUuid(); if (hostUuid == null) { hostUuid = spec.getVmInventory().getLastHostUuid(); } +if (hostUuid == null) { + logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] has no hostUuid/lastHostUuid, skip metadata cleanup", vmUuid)); + trigger.next(); + return; +} cmsg.setHostUuid(hostUuid);如果你更想走 GC 重试,这里还需要同步保证 GC 不再依赖同一条 host 定位链路;否则只是把失败延后。
🤖 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/VmExpungeMetadataFlow.java` around lines 89 - 93, 在 VmExpungeMetadataFlow 中不要把可能为 null 的 hostUuid 继续写入 cmsg:在使用 spec.getVmInventory().getHostUuid() / getLastHostUuid() 后,如果两者都为 null 就不要调用 cmsg.setHostUuid(...)(改为跳过设置并可选地记录一条 debug 日志),否则在根卷已被 expunge 的场景下 LocalStorage 无法通过 rootVolumeUuid 反查 host 会导致后续清理/GC 一直失败;定位到相关符号:VmExpungeMetadataFlow、spec.getVmInventory(), getHostUuid(), getLastHostUuid(), cmsg.setHostUuid() 并按此修改。plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (1)
3471-3474:⚠️ Potential issue | 🟠 Major把 backend 解析也纳入单主机失败分支。
Line 3472-3473 如果 host 在拿到
connectedHostUuids后被解绑或删除,这里会在bkd.handle()前直接抛异常,com既不会addError()也不会done(),整轮扫描可能被中断。🛠️ 最小修复方向
new While<>(connectedHostUuids).all((hostUuid, com) -> { - LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); - LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + final LocalStorageHypervisorBackend bkd; + try { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + com.addError(operr("failed to prepare vm metadata scan on host[uuid:%s] on local primary storage[uuid:%s]: %s", + hostUuid, self.getUuid(), e.getMessage())); + com.done(); + return; + } bkd.handle(msg, hostUuid, new ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>(com) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java` around lines 3471 - 3474, When iterating connectedHostUuids inside the While, resolve the hypervisor factory/backend and call bkd.handle inside a try/catch and treat null factory/backend as an error so the per-host Completion (com) is always resolved; specifically, in the block using getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory, LocalStorageHypervisorBackend and bkd.handle with ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>, catch any exception (or null returns) when obtaining the factory/backend or before invoking bkd.handle and call com.addError(...) and com.done() so a failure to resolve the backend for a single host does not leave com unresolved and block the overall scan.
🧹 Nitpick comments (3)
header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java (1)
31-31: 建议将resolver裸字符串收敛为常量
resolver = "volumeUuidToVmUuid"硬编码在多个 API 消息类中(至少 13 处),存在字符串漂移风险。建议定义一个常量类并在框架初始化时对 resolver Bean 可用性进行单次校验,以提升代码维护性。示例改法
-@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "volumeUuidToVmUuid", field = "rootVolumeUuid", updateOnFailure = true) +@MetadataImpact( + value = MetadataImpact.Impact.STORAGE, + resolver = MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID, + field = "rootVolumeUuid", + updateOnFailure = true +)// 新增:header/src/main/java/org/zstack/header/vm/metadata/MetadataImpactResolvers.java public final class MetadataImpactResolvers { private MetadataImpactResolvers() {} public static final String VOLUME_UUID_TO_VM_UUID = "volumeUuidToVmUuid"; }🤖 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/APICreateVolumeSnapshotGroupMsg.java` at line 31, The resolver name "volumeUuidToVmUuid" is hard-coded in the `@MetadataImpact` annotation (e.g., on APICreateVolumeSnapshotGroupMsg) and should be centralized as a constant; add a final utility class (e.g., MetadataImpactResolvers with VOLUME_UUID_TO_VM_UUID) and replace all literal occurrences of "volumeUuidToVmUuid" in annotations (including APICreateVolumeSnapshotGroupMsg) with the constant; additionally add a one-time startup validation in framework initialization to assert the resolver bean named by MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID is present (fail fast or log a clear error) so resolver availability is checked once at boot rather than relying on scattered string literals.header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java (1)
20-20: 建议在实现 MetadataImpact 处理器时避免直接使用 resolver 魔法字符串本 PR 新增 64 个
@MetadataImpact注解,涉及 7 种不同的 resolver 名称(如vmUuidDirectResolver、volumeUuidToVmUuid等),全部以字符串字面量形式指定。目前未发现对应的 Bean 实现或处理器,但一旦处理器启动时加载这些 resolver,拼写错误或名称不匹配将导致运行期NoSuchBeanDefinitionException。建议在实现处理器时:
- 使用常量类统一管理所有 resolver 名称(如
MetadataResolverNames.VM_UUID_DIRECT而非硬编码字符串)- 在处理器初始化或应用启动时进行 resolver 存在性校验,快速发现配置错误
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java` at line 20, The MetadataImpact annotations use literal resolver names (e.g., vmUuidDirectResolver in `@MetadataImpact` on APIDetachIsoFromVmInstanceMsg), which risks runtime NoSuchBeanDefinitionException; replace string literals with centralized constants (create a MetadataResolverNames class with constants like VM_UUID_DIRECT, VOLUME_UUID_TO_VM_UUID, etc.) and update all `@MetadataImpact` usages to reference those constants; additionally, add an initialization-time validation in the MetadataImpact processor (the class handling MetadataImpact annotations) to iterate required resolver names and assert the corresponding resolver beans exist, failing fast with a clear error if any are missing.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)
80-89: 考虑复用resolveBaseDir逻辑Lines 80-89 的查询逻辑与
resolveBaseDir()方法重复。虽然行为不同(此处返回空结果而非抛异常),但可以考虑提取一个带可选异常处理的私有方法来减少重复:可选重构
+ private String resolveBaseDirOrNull(String primaryStorageUuid) { + String baseDir = Q.New(PrimaryStorageVO.class) + .select(PrimaryStorageVO_.mountPath) + .eq(PrimaryStorageVO_.uuid, primaryStorageUuid) + .findValue(); + if (baseDir == null) { + baseDir = Q.New(PrimaryStorageVO.class) + .select(PrimaryStorageVO_.url) + .eq(PrimaryStorageVO_.uuid, primaryStorageUuid) + .findValue(); + } + return baseDir; + } + private String resolveBaseDir(String primaryStorageUuid) { - String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); - if (baseDir == null) { - baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); - } + String baseDir = resolveBaseDirOrNull(primaryStorageUuid); if (baseDir == null) { throw new CloudRuntimeException(...); } return baseDir.endsWith("/") ? baseDir.substring(0, baseDir.length() - 1) : baseDir; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java` around lines 80 - 89, The mountPath/url lookup in NfsVmMetadataExtension (the block that queries PrimaryStorageVO for mountPath or url using targetPsUuid and returns an empty PathReplacementResult) duplicates resolveBaseDir(); extract a single private helper (e.g., resolveBaseDirForUuid(String psUuid, boolean throwIfMissing) or resolveBaseDirOptional) that performs the two-step Q.New(...) select logic and returns the baseDir or null/throws based on a flag, then replace this duplicated block to call that helper and keep behavior here (return empty PathReplacementResult when null) while resolveBaseDir() can call the same helper with throwIfMissing=true to preserve existing exception behavior.
🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 41-60: 清晰一句话:当 hostUuid 为 null 时当前 GC 会持续发送必败的
CleanupVmInstanceMetadataOnPrimaryStorageMsg 导致不可自愈;修复方法是在发送前在 submitGC(或创建
CleanupVmInstanceMetadataOnPrimaryStorageMsg 的代码路径)检查 hostUuid,若为 null 则用
rootVolumeUuid 反查宿主机(通过查询卷/卷挂载/volume VO 得到宿主机 UUID);如果能查到则填充
msg.setHostUuid(foundHostUuid) 并继续 bus.send,否则不要发送消息并直接调用 completion.success()(或
cancel/成功结束 GC),以避免持久化一个必败请求。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 908-935: Add the missing import statements for the VM
metadata-related classes referenced in this file so it compiles: import the
GetVmInstanceMetadataFromPrimaryStorage* classes,
ScanVmInstanceMetadataFromPrimaryStorage* classes,
CleanupVmInstanceMetadataOnPrimaryStorage* classes, and the VmMetadataScanEntry
type (the same symbols used alongside UpdateVmInstanceMetadataOnPrimaryStorage*
and referenced in
WriteVmMetadataCmd/ScanVmMetadataRsp/GetVmInstanceMetadataRsp). Place these
imports with the other org.zstack storage/primary-related imports so the
compiler can resolve those types.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 337-372: The simulator is missing a RequestMapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH; add a new controller
method annotated with
`@RequestMapping`(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH,
method=RequestMethod.POST) that deserializes the request body into the
appropriate command class (e.g., RebaseBackingFilesCmd), appends it to the
simulator config's collection (e.g., config.rebaseBackingFilesCmds), sends a
reply via reply(entity, new RebaseBackingFilesRsp()) and returns null so tests
invoking handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) get a handled
response. Ensure the method signature and names match existing patterns used by
writeVmMetadata/getVmInstanceMetadata/scanVmMetadata/cleanupVmMetadata.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 134-145: createResourceRef currently unconditionally calls
dbf.persist on a new LocalStorageResourceRefVO which can cause a primary-key
conflict for the composite key (primaryStorageUuid, resourceUuid); modify
createResourceRef to first query the DB for an existing
LocalStorageResourceRefVO by primaryStorageUuid and resourceUuid (e.g. via
dbf.findByExample or a specific query), and if a record exists update its fields
(size, hostUuid, lastOpDate, etc.) and call dbf.update, otherwise persist the
new ref with dbf.persist; keep the method signature and ensure you use
LocalStorageResourceRefVO and dbf to locate and save/update the record.
- Around line 74-77: The current hard-coded marker matching in
VmInstanceMetadataConstants.extractOldPrefix() can mis-identify prefixes if
those marker substrings appear earlier in a mount path; update
extractOldPrefix() to locate markers from the path end (use lastIndexOf or scan
segments from the tail) or, preferably, resolve the real primary storage mount
point via primary storage metadata and compute the prefix boundary using that
exact mount path; ensure calculatePathReplacements() continues to rely on the
corrected extractOldPrefix() behavior and that callers such as
LocalStorageVmMetadataExtension (where oldPrefix is used) and
RebaseVolumeBackingFileOnPrimaryStorageMsg receive the accurate replacements to
avoid corrupting backing-file chains.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java`:
- Around line 990-994: The PrefixRebaseBackingFilesCmd currently only carries
filePaths, oldPrefix and newPrefix which allows an upstream prefix mis-detection
to be blindly applied; add source and target primary-storage mount path fields
(e.g., srcMountPath and destMountPath) to PrefixRebaseBackingFilesCmd and update
the agent handler that performs the rebasing to validate that each entry in
filePaths actually starts with oldPrefix and that the provided
srcMountPath/destMountPath match the agent’s mounted PS path before making any
changes; if any filePath fails the prefix check or mount-path mismatch, reject
the command with an error instead of applying the rewrite to backing files.
In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-618: The test harness is missing a simulator endpoint for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH used by
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg,...), so add a simulator(...)
block in LocalStorageSpec.groovy that registers PREFIX_REBASE_BACKING_FILES_PATH
and returns the matching response object (e.g. new
LocalStorageKvmBackend.RebaseVolumeBackingFileOnPrimaryStorageRsp()) so tests
exercising backing-file prefix rebases hit the mock agent.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-93: 在 VmExpungeMetadataFlow 中不要把可能为 null 的 hostUuid 继续写入
cmsg:在使用 spec.getVmInventory().getHostUuid() / getLastHostUuid() 后,如果两者都为 null
就不要调用 cmsg.setHostUuid(...)(改为跳过设置并可选地记录一条 debug 日志),否则在根卷已被 expunge 的场景下
LocalStorage 无法通过 rootVolumeUuid 反查 host 会导致后续清理/GC
一直失败;定位到相关符号:VmExpungeMetadataFlow、spec.getVmInventory(), getHostUuid(),
getLastHostUuid(), cmsg.setHostUuid() 并按此修改。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3471-3474: When iterating connectedHostUuids inside the While,
resolve the hypervisor factory/backend and call bkd.handle inside a try/catch
and treat null factory/backend as an error so the per-host Completion (com) is
always resolved; specifically, in the block using
getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorFactory,
LocalStorageHypervisorBackend and bkd.handle with
ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply>, catch any
exception (or null returns) when obtaining the factory/backend or before
invoking bkd.handle and call com.addError(...) and com.done() so a failure to
resolve the backend for a single host does not leave com unresolved and block
the overall scan.
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java`:
- Line 20: The MetadataImpact annotations use literal resolver names (e.g.,
vmUuidDirectResolver in `@MetadataImpact` on APIDetachIsoFromVmInstanceMsg), which
risks runtime NoSuchBeanDefinitionException; replace string literals with
centralized constants (create a MetadataResolverNames class with constants like
VM_UUID_DIRECT, VOLUME_UUID_TO_VM_UUID, etc.) and update all `@MetadataImpact`
usages to reference those constants; additionally, add an initialization-time
validation in the MetadataImpact processor (the class handling MetadataImpact
annotations) to iterate required resolver names and assert the corresponding
resolver beans exist, failing fast with a clear error if any are missing.
In
`@header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java`:
- Line 31: The resolver name "volumeUuidToVmUuid" is hard-coded in the
`@MetadataImpact` annotation (e.g., on APICreateVolumeSnapshotGroupMsg) and should
be centralized as a constant; add a final utility class (e.g.,
MetadataImpactResolvers with VOLUME_UUID_TO_VM_UUID) and replace all literal
occurrences of "volumeUuidToVmUuid" in annotations (including
APICreateVolumeSnapshotGroupMsg) with the constant; additionally add a one-time
startup validation in framework initialization to assert the resolver bean named
by MetadataImpactResolvers.VOLUME_UUID_TO_VM_UUID is present (fail fast or log a
clear error) so resolver availability is checked once at boot rather than
relying on scattered string literals.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 80-89: The mountPath/url lookup in NfsVmMetadataExtension (the
block that queries PrimaryStorageVO for mountPath or url using targetPsUuid and
returns an empty PathReplacementResult) duplicates resolveBaseDir(); extract a
single private helper (e.g., resolveBaseDirForUuid(String psUuid, boolean
throwIfMissing) or resolveBaseDirOptional) that performs the two-step Q.New(...)
select logic and returns the baseDir or null/throws based on a flag, then
replace this duplicated block to call that helper and keep behavior here (return
empty PathReplacementResult when null) while resolveBaseDir() can call the same
helper with throwIfMissing=true to preserve existing exception behavior.
🪄 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: 8364e772-ec3c-4833-a22b-5b53923d787c
⛔ Files ignored due to path filters (5)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/persistence.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xmlconf/serviceConfig/vmInstance.xmlis excluded by!**/*.xmlconf/springConfigXml/VmInstanceManager.xmlis excluded by!**/*.xml
📒 Files selected for processing (148)
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.javacompute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.javacompute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmSystemTags.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.javaheader/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.javaheader/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.javaheader/src/main/java/org/zstack/header/vm/VmInstanceState.javaheader/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.javaheader/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.javaheader/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.javaheader/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.javaheader/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.javaheader/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.javasdk/src/main/java/SourceClassMap.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovytestlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
✅ Files skipped from review due to trivial changes (97)
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- conf/db/upgrade/V5.0.0__schema.sql
- storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
🚧 Files skipped from review as they are similar to previous changes (22)
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java
- sdk/src/main/java/SourceClassMap.java
| CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | ||
| msg.setPrimaryStorageUuid(primaryStorageUuid); | ||
| msg.setVmUuid(vmUuid); | ||
| msg.setRootVolumeUuid(rootVolumeUuid); | ||
| msg.setMetadataPath(metadataPath); | ||
| msg.setHostUuid(hostUuid); | ||
|
|
||
| bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); | ||
| bus.send(msg, new CloudBusCallBack(completion) { | ||
| @Override | ||
| public void run(MessageReply reply) { | ||
| if (reply.isSuccess()) { | ||
| logger.info(String.format("[MetadataCleanupGC] successfully cleaned up metadata " + | ||
| "for vm[uuid:%s] on ps[uuid:%s]", vmUuid, primaryStorageUuid)); | ||
| completion.success(); | ||
| } else { | ||
| logger.warn(String.format("[MetadataCleanupGC] failed to clean up metadata " + | ||
| "for vm[uuid:%s] on ps[uuid:%s]: %s", vmUuid, primaryStorageUuid, reply.getError())); | ||
| completion.fail(reply.getError()); | ||
| } |
There was a problem hiding this comment.
hostUuid 为空时,这个 GC 会稳定重试失败。
VmExpungeMetadataFlow.submitGC() 会把 hostUuid 原样落到 GC;对于从未启动就删除的 VM,这里又把 null 继续塞进 CleanupVmInstanceMetadataOnPrimaryStorageMsg。本地存储后端随后按 hostUuid 路由 KVM 调用,结果只会反复失败,GC 无法自愈。建议在发送前先用 rootVolumeUuid 反查宿主机,查不到再 cancel()/success(),不要持久化一个必败请求。
🤖 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`
around lines 41 - 60, 清晰一句话:当 hostUuid 为 null 时当前 GC 会持续发送必败的
CleanupVmInstanceMetadataOnPrimaryStorageMsg 导致不可自愈;修复方法是在发送前在 submitGC(或创建
CleanupVmInstanceMetadataOnPrimaryStorageMsg 的代码路径)检查 hostUuid,若为 null 则用
rootVolumeUuid 反查宿主机(通过查询卷/卷挂载/volume VO 得到宿主机 UUID);如果能查到则填充
msg.setHostUuid(foundHostUuid) 并继续 bus.send,否则不要发送消息并直接调用 completion.success()(或
cancel/成功结束 GC),以避免持久化一个必败请求。
| public static class WriteVmMetadataCmd extends AgentCommand { | ||
| public String metadata; | ||
| public String metadataPath; | ||
| public String vmUuid; | ||
| public String vmName; | ||
| public String vmCategory; | ||
| public String architecture; | ||
| public String schemaVersion; | ||
| } | ||
|
|
||
| public static class WriteVmMetadataRsp extends AgentResponse { | ||
| } | ||
|
|
||
| public static class GetVmInstanceMetadataCmd extends AgentCommand { | ||
| public String metadataPath; | ||
| } | ||
|
|
||
| public static class GetVmInstanceMetadataRsp extends AgentResponse { | ||
| public String metadata; | ||
| } | ||
|
|
||
| public static class ScanVmMetadataCmd extends AgentCommand { | ||
| public String metadataDir; | ||
| } | ||
|
|
||
| public static class ScanVmMetadataRsp extends AgentResponse { | ||
| public List<VmMetadataScanEntry> metadataEntries = new ArrayList<>(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 先查看文件的导入部分
head -100 plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java | grep -E "^import"Repository: MatheMatrix/zstack
Length of output: 3472
🏁 Script executed:
# 搜索VmMetadataScanEntry类定义
fd -e java | xargs grep -l "class VmMetadataScanEntry"Repository: MatheMatrix/zstack
Length of output: 201
🏁 Script executed:
# 搜索GetVmInstanceMetadataFromPrimaryStorageMsg等消息类
fd -e java | xargs grep -l "class GetVmInstanceMetadataFromPrimaryStorageMsg\|class ScanVmInstanceMetadataFromPrimaryStorageMsg\|class CleanupVmInstanceMetadataOnPrimaryStorageMsg"Repository: MatheMatrix/zstack
Length of output: 375
🏁 Script executed:
# 检查LocalStorageKvmBackend.java在908-935和3878-3940行的内容
sed -n '908,935p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaRepository: MatheMatrix/zstack
Length of output: 927
🏁 Script executed:
# 检查3878-3940行
sed -n '3878,3940p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaRepository: MatheMatrix/zstack
Length of output: 3004
🏁 Script executed:
# 检查VmMetadataScanEntry的实际包名
head -20 header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java | grep "^package"Repository: MatheMatrix/zstack
Length of output: 104
🏁 Script executed:
# 检查GetVmInstanceMetadataFromPrimaryStorageMsg等类的实际包名
head -20 header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java | grep "^package"Repository: MatheMatrix/zstack
Length of output: 104
🏁 Script executed:
# 查找CleanupVmInstanceMetadataOnPrimaryStorageReply的位置
fd -e java | xargs grep -l "class CleanupVmInstanceMetadataOnPrimaryStorageReply"Repository: MatheMatrix/zstack
Length of output: 170
🏁 Script executed:
# 查找ScanVmInstanceMetadataFromPrimaryStorageReply的位置
fd -e java | xargs grep -l "class ScanVmInstanceMetadataFromPrimaryStorageReply"Repository: MatheMatrix/zstack
Length of output: 169
🏁 Script executed:
# 验证CleanupVmInstanceMetadataOnPrimaryStorageReply和ScanVmInstanceMetadataFromPrimaryStorageReply的包名
head -20 header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java | grep "^package"
head -20 header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java | grep "^package"Repository: MatheMatrix/zstack
Length of output: 147
🏁 Script executed:
# 检查是否还有其他Reply类也需要导入
fd -e java | xargs grep -l "class GetVmInstanceMetadataFromPrimaryStorageReply"Repository: MatheMatrix/zstack
Length of output: 168
🏁 Script executed:
# 最后验证一下LocalStorageKvmBackend.java中是否已有这些导入
grep -E "GetVmInstanceMetadataFromPrimaryStorageMsg|ScanVmInstanceMetadataFromPrimaryStorageMsg|CleanupVmInstanceMetadataOnPrimaryStorageMsg|VmMetadataScanEntry" plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java | head -20Repository: MatheMatrix/zstack
Length of output: 634
补齐 vm metadata 相关导入,否则本文件编译不过。
这里新增代码除了 UpdateVmInstanceMetadataOnPrimaryStorage*,还直接用了 GetVmInstanceMetadataFromPrimaryStorage*、ScanVmInstanceMetadataFromPrimaryStorage*、CleanupVmInstanceMetadataOnPrimaryStorage* 和 VmMetadataScanEntry;这些都不在当前包,当前 import 集合解析不到。
修复
import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg;
import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg;
+import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageReply;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageMsg;
+import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageReply;
+import org.zstack.header.storage.primary.ScanVmInstanceMetadataFromPrimaryStorageMsg;
+import org.zstack.header.storage.primary.ScanVmInstanceMetadataFromPrimaryStorageReply;
+import org.zstack.header.storage.primary.VmMetadataScanEntry;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`
around lines 908 - 935, Add the missing import statements for the VM
metadata-related classes referenced in this file so it compiles: import the
GetVmInstanceMetadataFromPrimaryStorage* classes,
ScanVmInstanceMetadataFromPrimaryStorage* classes,
CleanupVmInstanceMetadataOnPrimaryStorage* classes, and the VmMetadataScanEntry
type (the same symbols used alongside UpdateVmInstanceMetadataOnPrimaryStorage*
and referenced in
WriteVmMetadataCmd/ScanVmMetadataRsp/GetVmInstanceMetadataRsp). Place these
imports with the other org.zstack storage/primary-related imports so the
compiler can resolve those types.
| @RequestMapping(value=LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, method= RequestMethod.POST) | ||
| public @ResponseBody | ||
| String writeVmMetadata(HttpEntity<String> entity) { | ||
| WriteVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), WriteVmMetadataCmd.class); | ||
| config.writeVmMetadataCmds.add(cmd); | ||
| reply(entity, new WriteVmMetadataRsp()); | ||
| return null; | ||
| } | ||
|
|
||
| @RequestMapping(value=LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH, method= RequestMethod.POST) | ||
| public @ResponseBody | ||
| String getVmInstanceMetadata(HttpEntity<String> entity) { | ||
| GetVmInstanceMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVmInstanceMetadataCmd.class); | ||
| config.getVmInstanceMetadataCmds.add(cmd); | ||
| GetVmInstanceMetadataRsp rsp = new GetVmInstanceMetadataRsp(); | ||
| reply(entity, rsp); | ||
| return null; | ||
| } | ||
|
|
||
| @RequestMapping(value=LocalStorageKvmBackend.SCAN_VM_METADATA_PATH, method= RequestMethod.POST) | ||
| public @ResponseBody | ||
| String scanVmMetadata(HttpEntity<String> entity) { | ||
| ScanVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), ScanVmMetadataCmd.class); | ||
| config.scanVmMetadataCmds.add(cmd); | ||
| ScanVmMetadataRsp rsp = new ScanVmMetadataRsp(); | ||
| reply(entity, rsp); | ||
| return null; | ||
| } | ||
|
|
||
| @RequestMapping(value=LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH, method= RequestMethod.POST) | ||
| public @ResponseBody | ||
| String cleanupVmMetadata(HttpEntity<String> entity) { | ||
| CleanupVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CleanupVmMetadataCmd.class); | ||
| config.cleanupVmMetadataCmds.add(cmd); | ||
| reply(entity, new CleanupVmMetadataRsp()); | ||
| return null; |
There was a problem hiding this comment.
simulator 还缺少 PREFIX_REBASE_BACKING_FILES_PATH。
LocalStorageKvmBackend 本次同时新增了 handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...),并固定调用 PREFIX_REBASE_BACKING_FILES_PATH。这里却只补了 4 个 metadata 接口,没有对应 @RequestMapping,所以任何覆盖 backing file prefix rebase 的 simulator 用例都会收到未处理请求。
可参考的最小补齐
+ `@RequestMapping`(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, method= RequestMethod.POST)
+ public `@ResponseBody`
+ String prefixRebaseBackingFiles(HttpEntity<String> entity) {
+ PrefixRebaseBackingFilesCmd cmd = JSONObjectUtil.toObject(entity.getBody(), PrefixRebaseBackingFilesCmd.class);
+ PrefixRebaseBackingFilesRsp rsp = new PrefixRebaseBackingFilesRsp();
+ rsp.rebasedCount = cmd.filePaths == null ? 0 : cmd.filePaths.size();
+ reply(entity, rsp);
+ return null;
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`
around lines 337 - 372, The simulator is missing a RequestMapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH; add a new controller
method annotated with
`@RequestMapping`(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH,
method=RequestMethod.POST) that deserializes the request body into the
appropriate command class (e.g., RebaseBackingFilesCmd), appends it to the
simulator config's collection (e.g., config.rebaseBackingFilesCmds), sends a
reply via reply(entity, new RebaseBackingFilesRsp()) and returns null so tests
invoking handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) get a handled
response. Ensure the method signature and names match existing patterns used by
writeVmMetadata/getVmInstanceMetadata/scanVmMetadata/cleanupVmMetadata.
| for (String path : allOldPaths) { | ||
| oldPrefix = VmInstanceMetadataConstants.extractOldPrefix(path); | ||
| if (oldPrefix != null) break; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 检查 extractOldPrefix 的实现逻辑
ast-grep --pattern 'extractOldPrefix($$$) {
$$$
}'
# 搜索所有使用 extractOldPrefix 的位置
rg -n 'extractOldPrefix' --type javaRepository: MatheMatrix/zstack
Length of output: 565
🏁 Script executed:
# Get the implementation of extractOldPrefix in VmInstanceMetadataConstants
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/../../../header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaRepository: MatheMatrix/zstack
Length of output: 256
🏁 Script executed:
# Find and read VmInstanceMetadataConstants.java
fd VmInstanceMetadataConstants.java --type f -x cat -n {}Repository: MatheMatrix/zstack
Length of output: 1103
🏁 Script executed:
# Get context around lines 74-77 in LocalStorageVmMetadataExtension.java with more surrounding code
sed -n '55,96p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.javaRepository: MatheMatrix/zstack
Length of output: 2005
🏁 Script executed:
# Check the LocalStorageResourceRefVO and its constraints
rg -n 'class LocalStorageResourceRefVO' -A 20 --type javaRepository: MatheMatrix/zstack
Length of output: 4488
extractOldPrefix() 的硬编码标记匹配存在路径误识别风险。
VmInstanceMetadataConstants.extractOldPrefix() 使用硬编码的存储路径标记(/rootVolumes/、/dataVolumes/、/volumeSnapshots/、/memory/)通过 indexOf() 查找并提取主存储前缀。若主存储挂载路径本身包含这些子字符串(例如 /mnt/old_rootVolumes_backup/storage),则会导致提取出错误的短前缀。这将造成 calculatePathReplacements() 中的路径替换映射错误,进而影响 RebaseVolumeBackingFileOnPrimaryStorageMsg 的卷 backing file 链,可能导致数据损坏。
建议改进:从路径末尾向前扫描标记,或通过主节点查询实际的存储挂载路径来准确定位前缀界限。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`
around lines 74 - 77, The current hard-coded marker matching in
VmInstanceMetadataConstants.extractOldPrefix() can mis-identify prefixes if
those marker substrings appear earlier in a mount path; update
extractOldPrefix() to locate markers from the path end (use lastIndexOf or scan
segments from the tail) or, preferably, resolve the real primary storage mount
point via primary storage metadata and compute the prefix boundary using that
exact mount path; ensure calculatePathReplacements() continues to rely on the
corrected extractOldPrefix() behavior and that callers such as
LocalStorageVmMetadataExtension (where oldPrefix is used) and
RebaseVolumeBackingFileOnPrimaryStorageMsg receive the accurate replacements to
avoid corrupting backing-file chains.
| private void createResourceRef(String primaryStorageUuid, String resourceUuid, | ||
| String resourceType, String hostUuid, long size, Timestamp now) { | ||
| LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO(); | ||
| ref.setPrimaryStorageUuid(primaryStorageUuid); | ||
| ref.setResourceUuid(resourceUuid); | ||
| ref.setResourceType(resourceType); | ||
| ref.setHostUuid(hostUuid); | ||
| ref.setSize(size); | ||
| ref.setCreateDate(now); | ||
| ref.setLastOpDate(now); | ||
| dbf.persist(ref); | ||
| } |
There was a problem hiding this comment.
createResourceRef 未检查记录是否已存在,可能导致主键冲突。
LocalStorageResourceRefVO 以 (primaryStorageUuid, resourceUuid) 为复合主键。在 VM 元数据注册流程中,如果资源(卷或快照)已经存在于系统中并且已有对应的 LocalStorageResourceRefVO 记录,直接调用 dbf.persist() 会导致主键冲突异常。
🛡️ 建议修复:先检查记录是否存在
private void createResourceRef(String primaryStorageUuid, String resourceUuid,
String resourceType, String hostUuid, long size, Timestamp now) {
+ boolean exists = Q.New(LocalStorageResourceRefVO.class)
+ .eq(LocalStorageResourceRefVO_.primaryStorageUuid, primaryStorageUuid)
+ .eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid)
+ .isExists();
+ if (exists) {
+ logger.debug(String.format("LocalStorageResourceRefVO already exists for resource[uuid:%s] on PS[uuid:%s], skip creation",
+ resourceUuid, primaryStorageUuid));
+ return;
+ }
+
LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO();
ref.setPrimaryStorageUuid(primaryStorageUuid);
ref.setResourceUuid(resourceUuid);
ref.setResourceType(resourceType);
ref.setHostUuid(hostUuid);
ref.setSize(size);
ref.setCreateDate(now);
ref.setLastOpDate(now);
dbf.persist(ref);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`
around lines 134 - 145, createResourceRef currently unconditionally calls
dbf.persist on a new LocalStorageResourceRefVO which can cause a primary-key
conflict for the composite key (primaryStorageUuid, resourceUuid); modify
createResourceRef to first query the DB for an existing
LocalStorageResourceRefVO by primaryStorageUuid and resourceUuid (e.g. via
dbf.findByExample or a specific query), and if a record exists update its fields
(size, hostUuid, lastOpDate, etc.) and call dbf.update, otherwise persist the
new ref with dbf.persist; keep the method signature and ensure you use
LocalStorageResourceRefVO and dbf to locate and save/update the record.
| public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand { | ||
| public List<String> filePaths; | ||
| public String oldPrefix; | ||
| public String newPrefix; | ||
| } |
There was a problem hiding this comment.
为前缀重写命令补充挂载路径上下文,避免误重写 backing chain。
Line 990-994 仅传 oldPrefix/newPrefix,一旦上游前缀提取误判,agent 侧无法二次校验,会把错误映射直接应用到 backing files。建议在命令里补充源/目标 PS 挂载路径并在执行前校验 filePaths 与前缀一致性。
建议补充字段(最小改动)
public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand {
public List<String> filePaths;
public String oldPrefix;
public String newPrefix;
+ public String srcPsMountPath;
+ public String dstPsMountPath;
}📝 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.
| public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand { | |
| public List<String> filePaths; | |
| public String oldPrefix; | |
| public String newPrefix; | |
| } | |
| public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand { | |
| public List<String> filePaths; | |
| public String oldPrefix; | |
| public String newPrefix; | |
| public String srcPsMountPath; | |
| public String dstPsMountPath; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java`
around lines 990 - 994, The PrefixRebaseBackingFilesCmd currently only carries
filePaths, oldPrefix and newPrefix which allows an upstream prefix mis-detection
to be blindly applied; add source and target primary-storage mount path fields
(e.g., srcMountPath and destMountPath) to PrefixRebaseBackingFilesCmd and update
the agent handler that performs the rebasing to validate that each entry in
filePaths actually starts with oldPrefix and that the provided
srcMountPath/destMountPath match the agent’s mounted PS path before making any
changes; if any filePath fails the prefix check or mount-path mismatch, reject
the command with an error instead of applying the rewrite to backing files.
| simulator(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH) { | ||
| return new LocalStorageKvmBackend.WriteVmMetadataRsp() | ||
| } | ||
|
|
||
| simulator(LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH) { | ||
| return new LocalStorageKvmBackend.GetVmInstanceMetadataRsp() | ||
| } | ||
|
|
||
| simulator(LocalStorageKvmBackend.SCAN_VM_METADATA_PATH) { | ||
| return new LocalStorageKvmBackend.ScanVmMetadataRsp() | ||
| } | ||
|
|
||
| simulator(LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH) { | ||
| return new LocalStorageKvmBackend.CleanupVmMetadataRsp() | ||
| } |
There was a problem hiding this comment.
补上 PREFIX_REBASE_BACKING_FILES_PATH 的 testlib 模拟端点。
同一个 PR 里 LocalStorageKvmBackend 已新增 PREFIX_REBASE_BACKING_FILES_PATH,并在 handle(RebaseVolumeBackingFileOnPrimaryStorageMsg, ...) 中实际调用;这里却只注册了 4 个 metadata 路径。走本地存储 backing file prefix rebase 的用例在 LocalStorageSpec 环境下会直接打不到模拟 agent。
可参考的最小补齐
+ simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH) {
+ return new LocalStorageKvmBackend.PrefixRebaseBackingFilesRsp(rebasedCount: 0)
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy` around
lines 604 - 618, The test harness is missing a simulator endpoint for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH used by
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg,...), so add a simulator(...)
block in LocalStorageSpec.groovy that registers PREFIX_REBASE_BACKING_FILES_PATH
and returns the matching response object (e.g. new
LocalStorageKvmBackend.RebaseVolumeBackingFileOnPrimaryStorageRsp()) so tests
exercising backing-file prefix rebases hit the mock agent.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java (1)
17-29:⚠️ Potential issue | 🔴 Criticalresolver bean "snapshotGroupUuidToVmUuid" 未在代码库中实现,将导致运行时错误。
@MetadataImpact注解指定的 resolver bean "snapshotGroupUuidToVmUuid" 不存在于代码库中:
- VmUuidFromApiResolver 接口已定义,但没有任何实现类
- 该 resolver 在 4 个快照组 API 消息类中被引用(APIRevertVmFromSnapshotGroupMsg、APIDeleteVolumeSnapshotGroupMsg、APIUpdateVolumeSnapshotGroupMsg、APIUngroupVolumeSnapshotGroupMsg)
- 当元数据框架在运行时尝试加载该 bean 时,将抛出 NoSuchBeanDefinitionException
需要采取以下措施之一:
- 实现 VmUuidFromApiResolver 接口并将其注册为名称为 "snapshotGroupUuidToVmUuid" 的 Spring bean
- 如果暂不需要元数据跟踪,从所有快照组 API 消息中移除
@MetadataImpact注解🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java` around lines 17 - 29, The Metadata resolver "snapshotGroupUuidToVmUuid" referenced by the `@MetadataImpact` annotation is missing and causes runtime NoSuchBeanDefinitionException; either implement a Spring bean that implements the VmUuidFromApiResolver interface and register it with the exact bean name "snapshotGroupUuidToVmUuid" (create a class implementing VmUuidFromApiResolver and annotate it as a Spring component/service with name "snapshotGroupUuidToVmUuid") so the resolver is available for APIRevertVmFromSnapshotGroupMsg, APIDeleteVolumeSnapshotGroupMsg, APIUpdateVolumeSnapshotGroupMsg and APIUngroupVolumeSnapshotGroupMsg, or remove the `@MetadataImpact` annotation from those four message classes if metadata tracking is not required.
♻️ Duplicate comments (7)
testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy (1)
604-618:⚠️ Potential issue | 🟠 Major补上
PREFIX_REBASE_BACKING_FILES_PATH的 testlib 模拟端点。Line 604-618 这里只注册了 4 个 metadata 路径,但同一批改动里的 backing-file prefix rebase 路径在
LocalStorageSpec里仍然没有模拟。覆盖RebaseVolumeBackingFileOnPrimaryStorageMsg的用例会直接命中未处理请求。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy` around lines 604 - 618, 在 LocalStorageSpec 的模拟注册块中缺少对 LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH 的模拟端点,导致覆盖 RebaseVolumeBackingFileOnPrimaryStorageMsg 的测试直接触发未处理的请求;在与 WRITE_VM_METADATA_PATH / GET_VM_INSTANCE_METADATA_PATH / SCAN_VM_METADATA_PATH / CLEANUP_VM_METADATA_PATH 相同的位置添加一个 simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH) 条目并返回一个适当的响应对象(与其它模拟一致地返回对应的 LocalStorageKvmBackend 的响应类实例),以确保 RebaseVolumeBackingFileOnPrimaryStorageMsg 用例被正确拦截和处理。plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1989-1997:⚠️ Potential issue | 🟠 Major不要把
getConnectedHostForOperation()当成“返回空列表”的 API。Line 1991、Line 2016、Line 2041 和 Line 2066 都是先调用再判
isEmpty(),但同文件 Line 461-470 已经按抛OperationFailureException的语义处理过了。这里一旦无可用主机,异常会在bus.reply(...)前直接冒出,Get/Scan/Cleanup/Rebase四个消息都可能拿不到错误回复。🐛 建议修复
- List<HostInventory> connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); - if (connectedHosts.isEmpty()) { + List<HostInventory> connectedHosts; + try { + connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + } catch (OperationFailureException e) { reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); bus.reply(msg, reply); return; - } + }Also applies to: 2014-2023, 2039-2048, 2064-2073
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java` around lines 1989 - 1997, The handlers (e.g., handle(GetVmInstanceMetadataFromPrimaryStorageMsg)) call factory.getConnectedHostForOperation(...) and then check isEmpty(), but that factory method can throw an OperationFailureException (per earlier code paths), causing the method to throw before sending a reply; wrap the call in a try-catch for OperationFailureException (and other relevant runtime exceptions), create and populate the corresponding reply (GetVmInstanceMetadataFromPrimaryStorageReply and the other reply types for Scan/Cleanup/Rebase handlers), set reply.setError(...) using the caught exception details, and bus.reply(msg, reply) in the catch so every handler always returns an error reply instead of letting the exception propagate.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)
337-372:⚠️ Potential issue | 🟠 Majorsimulator 还缺
PREFIX_REBASE_BACKING_FILES_PATH。Line 337-372 这里只补了 4 个 metadata
@RequestMapping,但同一批改动里的 backing-file prefix rebase agent path 仍未注册。覆盖RebaseVolumeBackingFileOnPrimaryStorageMsg的 simulator 用例会收到未处理请求。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java` around lines 337 - 372, The simulator is missing a request mapping for LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH so requests from RebaseVolumeBackingFileOnPrimaryStorageMsg are unhandled; add a `@RequestMapping` handler in LocalStorageSimulator that listens on LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, deserializes the incoming command into the correct Cmd class used by the rebase agent, appends it to the simulator config list (e.g. config.prefixRebaseBackingFilesCmds or the appropriate list), and sends back the corresponding Rsp via reply(entity, new XxxRsp()) so the simulator test for RebaseVolumeBackingFileOnPrimaryStorageMsg is handled.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1863-1866:⚠️ Potential issue | 🟡 Minor先处理主存储已删除的竞态,避免误报为 unsupported
Line 1863 的
findValue()可能返回null。现在这里会把“主存储不存在”误报成type null does not support metadata,并把null继续传给getExtensionFromMap()。建议先对psType做空值短路。建议修复
- String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue(); - VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + String psType = Q.New(PrimaryStorageVO.class) + .select(PrimaryStorageVO_.type) + .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) + .findValue(); + if (psType == null) { + evt.setError(operr("primary storage[uuid:%s] not found", msg.getPrimaryStorageUuid())); + bus.publish(evt); + return; + } + + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); if (ext == null) { evt.setError(Platform.operr("primary storage type %s does not support metadata", psType)); bus.publish(evt); return; }As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法".
🤖 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/primary/PrimaryStorageBase.java` around lines 1863 - 1866, The code uses Q.New(...).findValue() to get psType and passes it to pluginRgty.getExtensionFromMap() without checking for null, which misreports deleted/nonexistent primary storage as "type null does not support metadata"; add an early-null check after String psType = Q.New(...).findValue() to handle a missing primary storage UUID (e.g. set evt.setError(Platform.operr(...)) and return) before calling pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class) so you don't pass null into getExtensionFromMap() or produce a misleading error.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3924-3928:⚠️ Potential issue | 🔴 Critical缺少
hostUuid空值短路会让元数据清理链路直接失败Line 3928 在
hostUuid可能为空时仍然发起httpCall()。本地存储场景下,这会让从未启动过就被删除的 VM 在清理元数据时直接失败,而不是平滑跳过/交给后续兜底逻辑处理。建议修复
`@Override` void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { + if (StringUtils.isBlank(hostUuid)) { + logger.warn(String.format("skip cleanup vm metadata on local primary storage[uuid:%s], metadataPath:%s because hostUuid is null/blank", + self.getUuid(), msg.getMetadataPath())); + completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply()); + return; + } + CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); cmd.metadataPath = msg.getMetadataPath(); httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) {As per coding guidelines "尽早返回(Early Return),将条件判断中的处理逻辑提前结束或抽成独立方法".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 3924 - 3928, In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) add an early-null check for hostUuid before creating the CleanupVmMetadataCmd or calling httpCall(CLEANUP_VM_METADATA_PATH,...); if hostUuid is null, short-circuit by completing successfully (e.g. completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply())) or otherwise returning so the cleanup is skipped/handled by downstream fallback logic, avoiding the failing httpCall when hostUuid is absent.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (2)
134-145:⚠️ Potential issue | 🟠 Major
createResourceRef()需要做幂等。这里每次都新建
LocalStorageResourceRefVO并dbf.persist()。注册、恢复或重试时,只要同一(primaryStorageUuid, resourceUuid)已经存在,就会直接撞主键,把元数据导入流程打断。建议先查现有记录,存在则更新hostUuid、size、lastOpDate,不存在再插入。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java` around lines 134 - 145, The createResourceRef method currently always inserts a new LocalStorageResourceRefVO which breaks on duplicate primary keys for the same (primaryStorageUuid, resourceUuid); make createResourceRef idempotent by first querying for an existing LocalStorageResourceRefVO by primaryStorageUuid and resourceUuid, and if found update its hostUuid, size and lastOpDate (and persist/merge/update as appropriate), otherwise construct and persist a new LocalStorageResourceRefVO; use the existing dbf query/update patterns (e.g., find by fields then dbf.update or dbf.persist) so retries/restore operations do not throw unique-key violations.
72-83:⚠️ Potential issue | 🔴 Critical不要直接信任
extractOldPrefix()的结果。这里把
VmInstanceMetadataConstants.extractOldPrefix(path)的返回值直接拿来做substring()切分。只要主存储挂载路径本身含有/rootVolumes/、/dataVolumes/之类的标记,oldPrefix就可能被截短,错误会扩散到整个pathMap,最终把 metadata 中的路径重写到错误位置,后面的 backing file rebase 也会跟着改坏。建议改成基于真实主存储挂载点计算边界,或至少从路径尾部匹配 marker。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java` around lines 72 - 83, The code currently trusts VmInstanceMetadataConstants.extractOldPrefix(path) and uses its result (oldPrefix) for substring slicing when building pathMap, which can produce incorrect prefixes if extractOldPrefix returns a truncated match; instead, compute the split boundary from the actual path content (e.g., locate the last occurrence of the known volume markers like "/rootVolumes/" or "/dataVolumes/" or derive the mount point boundary from the real primary storage mount path) and only treat a path as matching if the marker occurs at the expected boundary; update the loop that builds pathMap (references: allOldPaths, oldPrefix, pathMap, newPrefix, VmInstanceMetadataConstants.extractOldPrefix) to find the marker with lastIndexOf or validate against the actual storage mount point before calling substring so you never slice using an unverified oldPrefix.
🧹 Nitpick comments (4)
sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java (2)
31-35: 原始类型List缺少泛型参数
systemTags和userTags使用了原始类型java.util.List,缺少泛型参数,这会导致类型安全问题。根据 ZStack SDK 的常见用法,这些字段通常应为List<String>类型。如果这是 SDK 代码生成器的统一输出格式,建议在生成器侧修复此问题以提升类型安全性。
♻️ 建议添加泛型参数
`@Param`(required = false) -public java.util.List systemTags; +public java.util.List<String> systemTags; `@Param`(required = false) -public java.util.List userTags; +public java.util.List<String> userTags;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java` around lines 31 - 35, The fields systemTags and userTags in GetVmInstanceMetadataFromPrimaryStorageAction use raw java.util.List; change their declarations to use generics (java.util.List<String>) to ensure type safety, update any corresponding imports or usages (constructors, getters/setters, serialization code) to accept List<String>, and if this pattern is generated by the SDK generator, update the generator template for these fields to emit List<String> instead of raw List.
3-5: 冗余的通配符导入
org.zstack.sdk.*导入是冗余的,因为当前类已经在org.zstack.sdk包中。同包内的类会自动可见。不过,如果这是 SDK 代码生成器的标准输出模式,可以保持一致性而忽略此问题。
♻️ 建议移除冗余导入
package org.zstack.sdk; import java.util.HashMap; import java.util.Map; -import org.zstack.sdk.*;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java` around lines 3 - 5, 移除冗余的通配符导入:在 GetVmInstanceMetadataFromPrimaryStorageAction 类中删除 import org.zstack.sdk.*,因为类已位于 org.zstack.sdk 包内且同包类自动可见;保留需要的显式导入(例如 java.util.HashMap, java.util.Map)或恢复任何特定类的显式导入,如果这是代码生成器的约定且需要保持一致性则可选择保留但最好改为显式导入以避免冗余。sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java (1)
29-35: 建议将原始List改为List<String>,与服务端字段保持强类型一致Line 29、Line 32、Line 35 当前为 raw
java.util.List。这会弱化 SDK 编译期约束,允许非字符串元素混入,问题会推迟到运行期才暴露。建议改为List<String>。建议修改
- public java.util.List vmUuids; + public java.util.List<String> vmUuids; - public java.util.List systemTags; + public java.util.List<String> systemTags; - public java.util.List userTags; + public java.util.List<String> userTags;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java` around lines 29 - 35, The three public fields vmUuids, systemTags, and userTags in CleanupVmInstanceMetadataAction are declared as raw java.util.List which weakens compile-time type checks; change their types to java.util.List<String> in the CleanupVmInstanceMetadataAction class (and update any related getters/setters or usages in serialization/deserialization calls) so the SDK enforces String elements consistent with the server-side contract.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java (1)
7-9: 建议为接口方法添加 Javadoc 注释。根据编码规范,接口方法应配有有效的 Javadoc 注释。建议为
getPrimaryStorageType()和calculatePathReplacements()添加文档说明,描述参数含义和返回值语义。📝 建议的 Javadoc
public interface VmMetadataPathReplacementExtensionPoint { + /** + * Returns the primary storage type this extension handles. + * `@return` primary storage type identifier + */ String getPrimaryStorageType(); + + /** + * Calculates path replacements for migrating metadata paths from old prefix to new prefix. + * `@param` targetPsUuid the target primary storage UUID + * `@param` allOldPaths list of old paths to be replaced + * `@return` result containing old-to-new path mappings and prefix information + */ PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java` around lines 7 - 9, The interface VmMetadataPathReplacementExtensionPoint lacks Javadoc on its methods; add concise Javadoc to getPrimaryStorageType() explaining it returns the primary storage type identifier used by the extension, and to calculatePathReplacements(String targetPsUuid, List<String> allOldPaths) describing the parameters (targetPsUuid = destination primary storage UUID, allOldPaths = list of existing metadata paths) and the return semantics (PathReplacementResult containing mapping of old→new paths and any status/errors). Include `@param` and `@return` tags and a brief description of expected behavior for edge cases (e.g., empty list handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2168-2177: The handler method
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) must guard against a null/empty hostUuid before calling
hmsg.setHostUuid(...) and bus.makeTargetServiceIdByResourceUuid(...); update the
method to check hostUuid and if it's null/empty either select a fallback mounted
KVM host for this NFS primary (query available hosts/mounts) and set that host's
UUID on the KVMHostAsyncHttpCallMsg (and target service id) or, if none
available, skip direct routing and instead enqueue the cleanup for GC/retry
(return a success or scheduled response) so routing does not fail; reference
CLEANUP_VM_METADATA_PATH, KVMHostAsyncHttpCallMsg, setHostUuid, and
bus.makeTargetServiceIdByResourceUuid when making the changes.
---
Outside diff comments:
In
`@header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java`:
- Around line 17-29: The Metadata resolver "snapshotGroupUuidToVmUuid"
referenced by the `@MetadataImpact` annotation is missing and causes runtime
NoSuchBeanDefinitionException; either implement a Spring bean that implements
the VmUuidFromApiResolver interface and register it with the exact bean name
"snapshotGroupUuidToVmUuid" (create a class implementing VmUuidFromApiResolver
and annotate it as a Spring component/service with name
"snapshotGroupUuidToVmUuid") so the resolver is available for
APIRevertVmFromSnapshotGroupMsg, APIDeleteVolumeSnapshotGroupMsg,
APIUpdateVolumeSnapshotGroupMsg and APIUngroupVolumeSnapshotGroupMsg, or remove
the `@MetadataImpact` annotation from those four message classes if metadata
tracking is not required.
---
Duplicate comments:
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3924-3928: In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg
msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) add an early-null check for hostUuid before creating the
CleanupVmMetadataCmd or calling httpCall(CLEANUP_VM_METADATA_PATH,...); if
hostUuid is null, short-circuit by completing successfully (e.g.
completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply())) or
otherwise returning so the cleanup is skipped/handled by downstream fallback
logic, avoiding the failing httpCall when hostUuid is absent.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 337-372: The simulator is missing a request mapping for
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH so requests from
RebaseVolumeBackingFileOnPrimaryStorageMsg are unhandled; add a `@RequestMapping`
handler in LocalStorageSimulator that listens on
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, deserializes the
incoming command into the correct Cmd class used by the rebase agent, appends it
to the simulator config list (e.g. config.prefixRebaseBackingFilesCmds or the
appropriate list), and sends back the corresponding Rsp via reply(entity, new
XxxRsp()) so the simulator test for RebaseVolumeBackingFileOnPrimaryStorageMsg
is handled.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 134-145: The createResourceRef method currently always inserts a
new LocalStorageResourceRefVO which breaks on duplicate primary keys for the
same (primaryStorageUuid, resourceUuid); make createResourceRef idempotent by
first querying for an existing LocalStorageResourceRefVO by primaryStorageUuid
and resourceUuid, and if found update its hostUuid, size and lastOpDate (and
persist/merge/update as appropriate), otherwise construct and persist a new
LocalStorageResourceRefVO; use the existing dbf query/update patterns (e.g.,
find by fields then dbf.update or dbf.persist) so retries/restore operations do
not throw unique-key violations.
- Around line 72-83: The code currently trusts
VmInstanceMetadataConstants.extractOldPrefix(path) and uses its result
(oldPrefix) for substring slicing when building pathMap, which can produce
incorrect prefixes if extractOldPrefix returns a truncated match; instead,
compute the split boundary from the actual path content (e.g., locate the last
occurrence of the known volume markers like "/rootVolumes/" or "/dataVolumes/"
or derive the mount point boundary from the real primary storage mount path) and
only treat a path as matching if the marker occurs at the expected boundary;
update the loop that builds pathMap (references: allOldPaths, oldPrefix,
pathMap, newPrefix, VmInstanceMetadataConstants.extractOldPrefix) to find the
marker with lastIndexOf or validate against the actual storage mount point
before calling substring so you never slice using an unverified oldPrefix.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1989-1997: The handlers (e.g.,
handle(GetVmInstanceMetadataFromPrimaryStorageMsg)) call
factory.getConnectedHostForOperation(...) and then check isEmpty(), but that
factory method can throw an OperationFailureException (per earlier code paths),
causing the method to throw before sending a reply; wrap the call in a try-catch
for OperationFailureException (and other relevant runtime exceptions), create
and populate the corresponding reply
(GetVmInstanceMetadataFromPrimaryStorageReply and the other reply types for
Scan/Cleanup/Rebase handlers), set reply.setError(...) using the caught
exception details, and bus.reply(msg, reply) in the catch so every handler
always returns an error reply instead of letting the exception propagate.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1866: The code uses Q.New(...).findValue() to get psType and
passes it to pluginRgty.getExtensionFromMap() without checking for null, which
misreports deleted/nonexistent primary storage as "type null does not support
metadata"; add an early-null check after String psType = Q.New(...).findValue()
to handle a missing primary storage UUID (e.g. set
evt.setError(Platform.operr(...)) and return) before calling
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
so you don't pass null into getExtensionFromMap() or produce a misleading error.
In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-618: 在 LocalStorageSpec 的模拟注册块中缺少对
LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH 的模拟端点,导致覆盖
RebaseVolumeBackingFileOnPrimaryStorageMsg 的测试直接触发未处理的请求;在与
WRITE_VM_METADATA_PATH / GET_VM_INSTANCE_METADATA_PATH / SCAN_VM_METADATA_PATH /
CLEANUP_VM_METADATA_PATH 相同的位置添加一个
simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH)
条目并返回一个适当的响应对象(与其它模拟一致地返回对应的 LocalStorageKvmBackend 的响应类实例),以确保
RebaseVolumeBackingFileOnPrimaryStorageMsg 用例被正确拦截和处理。
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 7-9: The interface VmMetadataPathReplacementExtensionPoint lacks
Javadoc on its methods; add concise Javadoc to getPrimaryStorageType()
explaining it returns the primary storage type identifier used by the extension,
and to calculatePathReplacements(String targetPsUuid, List<String> allOldPaths)
describing the parameters (targetPsUuid = destination primary storage UUID,
allOldPaths = list of existing metadata paths) and the return semantics
(PathReplacementResult containing mapping of old→new paths and any
status/errors). Include `@param` and `@return` tags and a brief description of
expected behavior for edge cases (e.g., empty list handling).
In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java`:
- Around line 29-35: The three public fields vmUuids, systemTags, and userTags
in CleanupVmInstanceMetadataAction are declared as raw java.util.List which
weakens compile-time type checks; change their types to java.util.List<String>
in the CleanupVmInstanceMetadataAction class (and update any related
getters/setters or usages in serialization/deserialization calls) so the SDK
enforces String elements consistent with the server-side contract.
In
`@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java`:
- Around line 31-35: The fields systemTags and userTags in
GetVmInstanceMetadataFromPrimaryStorageAction use raw java.util.List; change
their declarations to use generics (java.util.List<String>) to ensure type
safety, update any corresponding imports or usages (constructors,
getters/setters, serialization code) to accept List<String>, and if this pattern
is generated by the SDK generator, update the generator template for these
fields to emit List<String> instead of raw List.
- Around line 3-5: 移除冗余的通配符导入:在 GetVmInstanceMetadataFromPrimaryStorageAction
类中删除 import org.zstack.sdk.*,因为类已位于 org.zstack.sdk 包内且同包类自动可见;保留需要的显式导入(例如
java.util.HashMap,
java.util.Map)或恢复任何特定类的显式导入,如果这是代码生成器的约定且需要保持一致性则可选择保留但最好改为显式导入以避免冗余。
🪄 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: 7e8a0351-ecc2-4481-be8d-de571aa9ea98
⛔ Files ignored due to path filters (5)
conf/globalConfig/vm.xmlis excluded by!**/*.xmlconf/persistence.xmlis excluded by!**/*.xmlconf/serviceConfig/primaryStorage.xmlis excluded by!**/*.xmlconf/serviceConfig/vmInstance.xmlis excluded by!**/*.xmlconf/springConfigXml/VmInstanceManager.xmlis excluded by!**/*.xml
📒 Files selected for processing (148)
compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.javacompute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.javacompute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.javacompute/src/main/java/org/zstack/compute/vm/VmSystemTags.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.javaheader/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.javaheader/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.javaheader/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.javaheader/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.javaheader/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.javaheader/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.javaheader/src/main/java/org/zstack/header/vm/VmInstanceState.javaheader/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.javaheader/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.javaheader/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.javaheader/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.javaheader/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.javaheader/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.javaheader/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.javaheader/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.javaheader/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.javaheader/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.javaheader/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.javaplugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.javaresourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.javasdk/src/main/java/SourceClassMap.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.javasdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.javasdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.javasdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.javasdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.javastorage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.javatestlib/src/main/java/org/zstack/testlib/ApiHelper.groovytestlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovytestlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
✅ Files skipped from review due to trivial changes (91)
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- conf/db/upgrade/V5.0.0__schema.sql
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- sdk/src/main/java/SourceClassMap.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java
🚧 Files skipped from review as they are similar to previous changes (35)
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
- testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
| public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { | ||
| CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); | ||
| cmd.setUuid(msg.getPrimaryStorageUuid()); | ||
| cmd.metadataPath = msg.getMetadataPath(); | ||
|
|
||
| KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); | ||
| hmsg.setCommand(cmd); | ||
| hmsg.setPath(CLEANUP_VM_METADATA_PATH); | ||
| hmsg.setHostUuid(hostUuid); | ||
| bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); |
There was a problem hiding this comment.
hostUuid 为空时不要继续路由清理请求。
未启动过就被删除的 VM 可能拿不到 hostUuid。这里直接把它用于 setHostUuid() 和 makeTargetServiceIdByResourceUuid(),会在路由阶段就把清理流程打断。对 NFS 来说更稳的是回退到任意可用挂载主机;至少也要先判空,再选择跳过本次清理或交给 GC。
🛠️ 一个最小兜底写法
`@Override`
public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+ if (StringUtils.isBlank(hostUuid)) {
+ logger.warn(String.format("skip cleaning vm metadata[path:%s] on nfs primary storage[uuid:%s] because no hostUuid is available",
+ msg.getMetadataPath(), msg.getPrimaryStorageUuid()));
+ completion.success(new CleanupVmInstanceMetadataOnPrimaryStorageReply());
+ return;
+ }
+
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.setUuid(msg.getPrimaryStorageUuid());
cmd.metadataPath = msg.getMetadataPath();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`
around lines 2168 - 2177, The handler method
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) must guard against a null/empty hostUuid before calling
hmsg.setHostUuid(...) and bus.makeTargetServiceIdByResourceUuid(...); update the
method to check hostUuid and if it's null/empty either select a fallback mounted
KVM host for this NFS primary (query available hosts/mounts) and set that host's
UUID on the KVMHostAsyncHttpCallMsg (and target service id) or, if none
available, skip direct routing and instead enqueue the cleanup for GC/retry
(return a success or scheduled response) so routing does not fail; reference
CLEANUP_VM_METADATA_PATH, KVMHostAsyncHttpCallMsg, setHostUuid, and
bus.makeTargetServiceIdByResourceUuid when making the changes.
APIImpact
Resolves: ZSV-11559
Change-Id: I6b6a6378627264646d6a76726762736e77787373
sync from gitlab !9463