<fix>[vm]: add MetadataImpact#3609
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough新增完整的 VM 元数据子系统:数据库表、GlobalConfig、运行时注解与解析器、API 消息/事件/文档、主存储(Local/NFS)后端与代理命令、SDK 与测试支持,以及元数据读写/扫描/清理/路径替换与相应的 GC/Flow 实现。 Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant API as API
participant Flow as VmExpungeMetadataFlow
participant PS as PrimaryStorageService
participant Host as Host Agent
participant DB as Database
Client->>API: DELETE VM
API->>Flow: 触发 VmExpungeMetadataFlow
Flow->>PS: CleanupVmInstanceMetadataOnPrimaryStorageMsg
PS->>Host: 转发到主机代理清理接口 (hostUuid)
Host-->>PS: CleanupVmMetadataRsp
PS-->>Flow: CleanupVmInstanceMetadataOnPrimaryStorageReply
alt 成功
Flow->>API: trigger.next()
else 失败
Flow->>DB: 提交 CleanupVmInstanceMetadataOnPrimaryStorageGC(重试/GC)
end
sequenceDiagram
participant Client as Client
participant API as API
participant PS as PrimaryStorageService
participant HostA as Host A
participant HostB as Host B
participant DB as Database
Client->>API: GET /primary-storage/vm-instances/metadata/scan?uuid=psUuid
API->>PS: ScanVmInstanceMetadataFromPrimaryStorageMsg(metadataDir)
PS->>HostA: 发送扫描请求
PS->>HostB: 发送扫描请求
HostA-->>PS: 返回 VmMetadataScanEntry 列表 (hostUuid set)
HostB-->>PS: 返回 VmMetadataScanEntry 列表 (hostUuid set)
PS->>DB:(可选)更新 fingerprint/dirty VO
PS-->>API: 合并并过滤(去除 TEMPLATE_CACHE)后返回扫描结果
API-->>Client: 返回扫描事件
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
|
Comment from yaohua.wu: Cross-Repo Review: MR !9469 (zstack) + !13338 (premium) + !6820 (utility) — ZSV-11559Jira: ZSV-11559 — 【注册虚拟机】(New Feature: VM Registration)
🔴 Critical
🟡 Warning
🟢 Suggestion
Verdict: APPROVED with warnings整体架构设计合理: 🤖 Robot Reviewer |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (17)
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java (1)
18-22: 将code改为final,并使用%d替换%s。code 字段仅在枚举构造器中赋值一次,应改为 final 以确保不可变性;id 是整型,用
%d格式符更准确。♻️ 建议修改
- private String code; + private final String code; private VmMetadataErrors(int id) { - code = String.format("VM_METADATA.%s", id); + code = String.format("VM_METADATA.%d", id); }🤖 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 18 - 22, Make the private field code in the VmMetadataErrors enum final and update the constructor to format the integer id with %d instead of %s; specifically change the declaration of code to "private final String code" and update the VmMetadataErrors(int id) constructor to use String.format("VM_METADATA.%d", id) so code is immutable and formatted correctly.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java (1)
3-8: 接口方法缺少 Javadoc 注释根据编码规范,接口方法必须配有有效的 Javadoc 注释。建议为每个方法添加说明文档,特别是
validateMetadataPath方法的返回值语义不够清晰(返回验证后的路径?错误信息?成功时返回 null?)。📝 建议添加 Javadoc 注释
public interface VmMetadataPathBuildExtensionPoint { + /** + * 获取此扩展点支持的主存储类型 + * `@return` 主存储类型标识符 + */ String getPrimaryStorageType(); + + /** + * 构建 VM 元数据文件路径 + * `@param` primaryStorageUuid 主存储 UUID + * `@param` vmInstanceUuid VM 实例 UUID + * `@return` 元数据文件完整路径 + */ String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid); + + /** + * 构建元数据目录路径 + * `@param` primaryStorageUuid 主存储 UUID + * `@return` 元数据目录路径 + */ String buildMetadataDir(String primaryStorageUuid); + + /** + * 验证元数据路径的有效性 + * `@param` primaryStorageUuid 主存储 UUID + * `@param` path 待验证的路径 + * `@return` 验证通过返回规范化后的路径,验证失败返回 null 或抛出异常(请明确语义) + */ String validateMetadataPath(String primaryStorageUuid, String path); }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/VmMetadataPathBuildExtensionPoint.java` around lines 3 - 8, Add Javadoc comments for every method in the VmMetadataPathBuildExtensionPoint interface: document getPrimaryStorageType(), buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid), buildMetadataDir(String primaryStorageUuid), and especially validateMetadataPath(String primaryStorageUuid, String path) — explicitly state the return semantics (e.g. returns normalized/validated path on success, or null/throws/returns error message on failure) and describe parameters and expected behavior; keep methods without explicit modifiers (interface methods remain implicitly public) and place the Javadoc directly above each method declaration.header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java (1)
27-41: 批量解析结果建议去重,避免重复处理同一 VM当前实现会原样累加,若多个输入映射到同一 VM UUID,会产生重复结果。建议用
LinkedHashSet去重后再返回。♻️ 建议修改
- default List<String> batchResolveVmUuids(List<String> fieldValues) { - List<String> result = new ArrayList<>(); + default List<String> batchResolveVmUuids(List<String> fieldValues) { + java.util.Set<String> result = new java.util.LinkedHashSet<>(); if (fieldValues == null || fieldValues.isEmpty()) { - return result; + return new ArrayList<>(); } for (String v : fieldValues) { if (v == null) { continue; } List<String> resolved = resolveVmUuids(v); if (resolved != null) { result.addAll(resolved); } } - return result; + return new ArrayList<>(result); }🤖 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/VmUuidFromApiResolver.java` around lines 27 - 41, The batchResolveVmUuids method currently accumulates resolved UUIDs into a List allowing duplicates; change the aggregation to preserve insertion order and remove duplicates by collecting results into a LinkedHashSet (or equivalent) while iterating over fieldValues and resolveVmUuids(v), then return a List built from that set; reference batchResolveVmUuids and resolveVmUuids to locate the code and ensure null checks and skipping null fieldValues remain unchanged.sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java (1)
22-27: 为failedVmUuids补充泛型,避免 SDK 调用端强制转型使用 raw
List会丢失编译期类型检查,建议改为List<String>(同类问题也出现在ScanVmInstanceMetadataFromPrimaryStorageResult.vmInstanceMetadata,可在代码生成模板统一修复)。♻️ 建议修改
- public java.util.List failedVmUuids; - public void setFailedVmUuids(java.util.List failedVmUuids) { + public java.util.List<String> failedVmUuids; + public void setFailedVmUuids(java.util.List<String> failedVmUuids) { this.failedVmUuids = failedVmUuids; } - public java.util.List getFailedVmUuids() { + public java.util.List<String> getFailedVmUuids() { return this.failedVmUuids; }🤖 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/CleanupVmInstanceMetadataResult.java` around lines 22 - 27, The field and its accessor signatures in CleanupVmInstanceMetadataResult should use a generic type to avoid raw List usage: change the field failedVmUuids and its setter/getter signatures from java.util.List to java.util.List<String> (update the field declaration, setFailedVmUuids(List<String> failedVmUuids), and getFailedVmUuids(): List<String>) and regenerate/compile; also apply the same change for ScanVmInstanceMetadataFromPrimaryStorageResult.vmInstanceMetadata to use a typed List (e.g., List<String> or the appropriate element type) in the codegen template so callers don't need unchecked casts.conf/db/upgrade/V5.0.0__schema.sql (1)
16-24:VmMetadataFingerprintVO表缺少lastOpDate和createDate审计列。
VmMetadataDirtyVO表包含lastOpDate和createDate时间戳列,但VmMetadataFingerprintVO表没有这些审计列。建议保持一致性,添加相同的时间戳列以便于跟踪记录变更。♻️ 建议添加审计列
CREATE TABLE IF NOT EXISTS `zstack`.`VmMetadataFingerprintVO` ( `vmInstanceUuid` VARCHAR(32) NOT NULL, `metadataSnapshot` LONGTEXT, `lastFlushTime` TIMESTAMP NULL DEFAULT NULL, `lastFlushFailed` TINYINT(1) NOT NULL DEFAULT 0, `staleRecoveryCount` INT NOT NULL DEFAULT 0, + `lastOpDate` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`vmInstanceUuid`), CONSTRAINT `fkVmMetadataFingerprintVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;🤖 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 16 - 24, The VmMetadataFingerprintVO table is missing the audit timestamp columns present on VmMetadataDirtyVO; update the CREATE TABLE for VmMetadataFingerprintVO to add the same audit columns (createDate and lastOpDate) with appropriate TIMESTAMP types and defaults (e.g., createDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP and lastOpDate TIMESTAMP NULL DEFAULT NULL or with ON UPDATE CURRENT_TIMESTAMP if used in your schema) so the table matches VmMetadataDirtyVO’s auditing schema while keeping the existing PRIMARY KEY and FK constraint fkVmMetadataFingerprintVOVmInstanceEO intact.compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java (1)
1336-1346: 建议先检查主存储是否存在。当前代码在查询
PrimaryStorageVO时,如果psUuid对应的主存储不存在,psType会是null。虽然后续逻辑会因为找不到扩展点而报错,但错误信息"primary storage[uuid:xxx, type:null] does not support vm metadata"会让用户困惑。建议在类型查询前先验证主存储是否存在,或者在
psType为null时提供更清晰的错误信息:♻️ 建议优化错误处理
// Delegate path validation to the storage-type-specific extension String psUuid = msg.getPrimaryStorageUuid(); String psType = Q.New(PrimaryStorageVO.class) .select(PrimaryStorageVO_.type) .eq(PrimaryStorageVO_.uuid, psUuid) .findValue(); + if (psType == null) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s] not found", psUuid)); + } VmMetadataPathBuildExtensionPoint ext = (psType != null) ? pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class) : null;🤖 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/VmInstanceApiInterceptor.java` around lines 1336 - 1346, The code in VmInstanceApiInterceptor should explicitly check that the PrimaryStorageVO exists before assuming a type; change the lookup to fetch the whole PrimaryStorageVO (e.g., Q.New(PrimaryStorageVO.class).eq(PrimaryStorageVO_.uuid, psUuid).find()) and if the returned VO is null throw an ApiMessageInterceptionException with a clear message like "primary storage[uuid:%s] not found"; otherwise read vo.getType() into psType and then proceed to get the VmMetadataPathBuildExtensionPoint from pluginRgty and, if missing, throw the existing "does not support vm metadata" error including the actual non-null type. Ensure you reference msg.getPrimaryStorageUuid(), PrimaryStorageVO/PrimaryStorageVO_.type, pluginRgty, VmMetadataPathBuildExtensionPoint, ApiMessageInterceptionException and argerr when applying the change.header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java (1)
30-33: 建议在__example__()中统一走 setter 赋值。当前直接访问成员字段可工作,但和类内其余赋值风格不一致;建议改为
setVmUuids(...)。♻️ 建议修改
public static APIUpdateVmInstanceMetadataMsg __example__() { APIUpdateVmInstanceMetadataMsg msg = new APIUpdateVmInstanceMetadataMsg(); - msg.vmUuids = Collections.singletonList(uuid()); + msg.setVmUuids(Collections.singletonList(uuid())); return msg; }🤖 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/APIUpdateVmInstanceMetadataMsg.java` around lines 30 - 33, The __example__() method sets vmUuids by assigning the field directly (msg.vmUuids = ...) which is inconsistent with the rest of the class; update __example__() to use the setter instead: call msg.setVmUuids(Collections.singletonList(uuid())) so the example follows the class's accessor pattern and any validation/logic in setVmUuids is applied.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1863-1864: 可直接使用self.getType(),避免一次额外 DB 查询。当前实例已绑定具体主存储,类型通常可直接从
self读取,逻辑更直接也更轻量。♻️ 建议修改
-String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue(); +String psType = self.getType();🤖 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 - 1864, Replace the extra DB query that obtains psType with the instance's stored type: use self.getType() (from PrimaryStorageBase) instead of calling Q.New(PrimaryStorageVO.class)...findValue(); then pass that type into pluginRgty.getExtensionFromMap(..., VmMetadataPathBuildExtensionPoint.class). In other words, remove the Q.New(...) lookup and call self.getType() (or this.getType()) when resolving the extension in PrimaryStorageBase so pluginRgty.getExtensionFromMap uses the in-memory type.compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
95-96: 冗余的 final 变量赋值
psUuid在第 48-54 行之后已经是有效的 final 变量(effectively final),不需要再赋值给finalPsUuid。可以直接在 lambda 中使用psUuid。♻️ 建议简化
- final String finalPsUuid = psUuid; final String finalHostUuid = hostUuid; bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); bus.send(cmsg, new CloudBusCallBack(trigger) { `@Override` public void run(MessageReply reply) { if (reply.isSuccess()) { logger.info(String.format("[MetadataExpunge] successfully deleted metadata for vm[uuid:%s] on ps[uuid:%s]", - vmUuid, finalPsUuid)); + vmUuid, psUuid)); } else { logger.warn(String.format("[MetadataExpunge] failed to delete metadata for vm[uuid:%s] on ps[uuid:%s]: %s, " + - "submitting GC job for retry", vmUuid, finalPsUuid, reply.getError())); - submitGC(finalPsUuid, vmUuid, rootVolumeUuid, metadataPath, finalHostUuid); + "submitting GC job for retry", vmUuid, psUuid, reply.getError())); + submitGC(psUuid, vmUuid, rootVolumeUuid, metadataPath, finalHostUuid); }🤖 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 95 - 96, 在 VmExpungeMetadataFlow.java 中删除多余的 final 变量赋值(即移除 `final String finalPsUuid = psUuid;` 和 `final String finalHostUuid = hostUuid;`),直接在后续的 lambda/匿名内部类中使用已是 effectively final 的 `psUuid` 和 `hostUuid`。也请检查所有引用 `finalPsUuid` 和 `finalHostUuid` 的位置并改为直接使用 `psUuid` 和 `hostUuid`,确保编译通过并且语义不变(相关符号:psUuid, hostUuid, finalPsUuid, finalHostUuid, 相关 lambda/匿名类)。header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java (1)
16-17: 建议将字段命名为vmInstanceUuid以提高可读性字段命名为
uuid不够明确,考虑到@APIParam指定了resourceType = VmInstanceVO.class,建议重命名为vmInstanceUuid以更清晰地表达其含义,并与VmInstanceMessage接口方法getVmInstanceUuid()保持一致。♻️ 建议修改
`@APIParam`(resourceType = VmInstanceVO.class) - 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; + public String getVmInstanceUuid() { + return vmInstanceUuid; }As per coding guidelines: "命名应尽量用完整的单词组合表达意图"。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java` around lines 16 - 17, Rename the ambiguous field uuid in APIGetVmInstanceMetadataFromPrimaryStorageMsg to vmInstanceUuid: change the private field name, its getter/setter, and any usages to vmInstanceUuid, keep the `@APIParam`(resourceType = VmInstanceVO.class) annotation on the field, and ensure the class implements/overrides VmInstanceMessage.getVmInstanceUuid() (and update any callers or marshalling logic that referenced the old uuid symbol) so names remain consistent across the codebase.header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java (1)
10-12: 考虑使用原始类型int替代Integer包装类型根据 ZStack 代码规范,数值字段优先使用原始类型(如
int)而非包装类型(Integer),以避免 NPE 风险和不必要的装箱/拆箱操作。如果字段可能未设置,应在设置端确保默认值。♻️ 建议修改
- private Integer totalCleaned; - private Integer totalFailed; + private int totalCleaned; + private int totalFailed;Based on learnings: "在 ZStack 代码库中,当响应类包含数值字段时,优先使用原始类型而不是包装类型,以避免 NPE 风险和不必要的装箱/拆箱操作。"
🤖 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/APICleanupVmInstanceMetadataEvent.java` around lines 10 - 12, Change the numeric wrapper fields in APICleanupVmInstanceMetadataEvent from Integer to primitive int to follow ZStack conventions: replace declarations totalCleaned and totalFailed with primitive int, adjust any corresponding getters/setters and usages to work with int (initialize defaults if needed), and leave failedVmUuids as List<String>; ensure no code expects null for these fields.sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java (1)
79-85: 布尔类型 getter 方法命名建议Java Bean 规范中,布尔类型的 getter 方法通常使用
is前缀而非get前缀。建议将getIncomplete()重命名为isIncomplete()。♻️ 建议修改
- 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, Rename the boolean getter in class VmMetadataScanEntry from getIncomplete() to isIncomplete() to follow JavaBean conventions: keep the field incomplete and the setter setIncomplete(boolean), add or replace the existing getIncomplete() method with a public boolean isIncomplete() method, and update any references/call sites to use isIncomplete() (or keep getIncomplete() as a deprecated wrapper if backward compatibility is required).plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java (1)
96-104: 给新增接口方法补上 Javadoc,明确hostUuid的约束。这 5 个方法都把主机选择外提给调用方,但接口本身没说明
hostUuid是否必须、是否允许任意 connected host、以及失败时的约定,后续实现很容易各自理解一套。As per coding guidelines:
接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。🤖 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/NfsPrimaryStorageBackend.java` around lines 96 - 104, Add Javadoc to each of the five interface methods (handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...), handle(GetVmInstanceMetadataFromPrimaryStorageMsg...), handle(ScanVmInstanceMetadataFromPrimaryStorageMsg...), handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...), handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)) that documents: (1) hostUuid is required (non-null) and must be the UUID of a host that is connected to the primary storage and where the operation will be executed (caller is responsible for selecting such a host); (2) what the method does at a high level; and (3) the failure contract — implementations must report errors via the provided ReturnValueCompletion (e.g., completion.fail on any local/remote failure) and must not throw unchecked exceptions. Ensure the Javadoc follows the project style and add no extra modifiers to the interface methods.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
944-952: 内部类中存在冗余的类名限定符。
PrefixRebaseBackingFilesCmd和PrefixRebaseBackingFilesRsp继承时使用了完整类名LocalStorageKvmBackend.AgentCommand和LocalStorageKvmBackend.AgentResponse,但由于它们已在LocalStorageKvmBackend类内部定义,可以直接使用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, Remove the redundant outer-class qualifiers when declaring the inner classes: change the extends clauses of PrefixRebaseBackingFilesCmd and PrefixRebaseBackingFilesRsp to extend AgentCommand and AgentResponse (respectively) instead of LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse so the inner classes use the enclosing class's types directly; update only the class declarations for PrefixRebaseBackingFilesCmd and PrefixRebaseBackingFilesRsp.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)
80-89:calculatePathReplacements中存在与resolveBaseDir重复的逻辑。此处的
baseDir解析逻辑(先查mountPath,再查url)与resolveBaseDir方法重复。建议复用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 code in calculatePathReplacements duplicates baseDir resolution (checking mountPath then url); replace that block by calling resolveBaseDir(targetPsUuid) and handle any exception it throws; if resolveBaseDir returns null behave as before (logger.warn(...) and return a PathReplacementResult with empty metadata map), and if it throws, log the error and return the same empty PathReplacementResult—update references inside calculatePathReplacements to use the resolved value and preserve existing variables (targetPsUuid, PathReplacementResult) and logging.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (2)
24-146:LocalStorageVmMetadataExtension与NfsVmMetadataExtension实现高度相似。这两个类的
calculatePathReplacements、validateMetadataPath等方法实现几乎相同,仅在获取基础路径的来源上有差异(local 用url,NFS 用mountPath或url)。如果后续有更多存储类型需要支持,建议考虑提取抽象基类以减少代码重复。🤖 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 24 - 146, Extract the duplicated logic in calculatePathReplacements, validateMetadataPath, buildMetadataDir/buildVmMetadataPath string construction, normalizeBaseDir and createResourceRef into a new abstract base class (e.g., AbstractVmMetadataExtension) that implements VmMetadataPathBuildExtensionPoint, VmMetadataPathReplacementExtensionPoint and VmMetadataResourcePersistExtensionPoint; add an abstract helper method like getBaseDir(String primaryStorageUuid) that subclasses implement (LocalStorageVmMetadataExtension returns PrimaryStorageVO_.url while NfsVmMetadataExtension returns mountPath or url as needed), move shared constants use (VmInstanceMetadataConstants) and path normalization there, keep DB persistence (dbf.persist) either in the base createResourceRef or callable from base, and update LocalStorageVmMetadataExtension and NfsVmMetadataExtension to extend the new base and only implement the storage-specific base-dir retrieval and any small differences.
37-53:buildMetadataDir和buildVmMetadataPath存在重复的数据库查询。这两个方法都单独查询
PrimaryStorageVO.url,当连续调用时会产生重复查询。考虑提取公共方法或在调用方缓存结果。♻️ 建议提取公共方法
+ private String resolveUrl(String primaryStorageUuid) { + String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + if (url == null) { + throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid)); + } + return normalizeBaseDir(url); + } + `@Override` public String buildMetadataDir(String primaryStorageUuid) { - String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); - if (url == null) { - throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid)); - } - return String.format("%s/%s", normalizeBaseDir(url), VmInstanceMetadataConstants.METADATA_DIR_NAME); + return String.format("%s/%s", resolveUrl(primaryStorageUuid), VmInstanceMetadataConstants.METADATA_DIR_NAME); } `@Override` public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) { - String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); - if (url == null) { - throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid)); - } - return String.format("%s/%s/%s%s", normalizeBaseDir(url), VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX); + return String.format("%s/%s/%s%s", resolveUrl(primaryStorageUuid), VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX); }🤖 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 37 - 53, Both buildMetadataDir and buildVmMetadataPath duplicate the PrimaryStorageVO.url query; extract a private helper (e.g., getNormalizedBaseDir or fetchNormalizedUrl) that runs the Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(), throws the same CloudRuntimeException if null, and returns normalizeBaseDir(url); then have buildMetadataDir call that helper and append VmInstanceMetadataConstants.METADATA_DIR_NAME, and have buildVmMetadataPath call the helper and build the full path with VmInstanceMetadataConstants.METADATA_DIR_NAME and VmInstanceMetadataConstants.FILE_METADATA_SUFFIX to remove the duplicated query logic.
🤖 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/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 8-10: Add clear method-level Javadoc to the
VmMetadataPathReplacementExtensionPoint interface: document
getPrimaryStorageType() to specify the expected returned storage type string and
whether null/empty is allowed; document calculatePathReplacements(String
targetPsUuid, List<String> allOldPaths) to describe semantics of targetPsUuid
and allOldPaths (non-null, expected format), what the implementation must
compute, concurrency expectations, error handling and whether it may throw
exceptions or should return an empty PathReplacementResult on no changes; and
document the nested PathReplacementResult class to explain fields, meaning of
empty vs null collections, and how callers should interpret the result.
Reference the methods getPrimaryStorageType(), calculatePathReplacements(...),
and the PathReplacementResult class when adding the Javadoc so implementors have
unambiguous input/output contracts.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java`:
- Around line 6-10: The interface VmMetadataResourcePersistExtensionPoint and
its methods getPrimaryStorageType, afterVolumePersist, and afterSnapshotPersist
lack Javadoc; add concise Javadoc for each method at the interface level (no
extra modifiers) describing purpose, caller contract, exact parameter semantics
(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now), expected
return/side-effects, and when the method is invoked (e.g., after a volume or
snapshot is persisted to primary storage), plus any threading or exception
expectations so implementers have a clear, uniform contract.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3413-3437: The current flow treats a supplied but no-longer-valid
hostUuid as an ordinary error; update LocalStorageBase to (1) for
GetVmInstanceMetadataFromPrimaryStorageMsg: if msg.getHostUuid() is non-null but
lookup of host or hypervisor backend indicates the host is
deleted/unbound/invalid, fall back to resolving host via
getHostUuidByResourceUuid(msg.getRootVolumeUuid()) and proceed with the backend
from getHypervisorBackendFactoryByHostUuid(resolvedHostUuid) before failing; (2)
for CleanupVmInstanceMetadataOnPrimaryStorageMsg: if the persisted hostUuid is
invalid/terminal (host deleted or detached), consider the cleanup a no-op
success (or cancel GC) instead of retrying—detect this via the same host
lookup/validation and return success to the bus; change error branches around
getHostUuidByResourceUuid, getHypervisorBackendFactoryByHostUuid, and bkd.handle
to implement these two behaviors.
- Around line 3358-3367: The ChainTask submitted via thdf.chainSubmit uses
getSyncSignature() == "update-metadata-on-ps-" + self.getUuid() but
getSyncLevel() returns 10, which allows up to 10 concurrent metadata writes for
the same local primary storage and can cause race/overwrite on file-based
metadata; change the synchronization to serialize by either returning a
per-VM/per-root-volume signature (e.g. include rootVolumeUuid or vmUuid in
getSyncSignature()) or reduce getSyncLevel() to 1 to force single concurrency;
update the anonymous ChainTask in LocalStorageBase (the thdf.chainSubmit block
implementing getSyncSignature and getSyncLevel) accordingly so same
VM/rootVolume operations are bucketed or fully serialized.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 346-363: 在 getVmInstanceMetadata 和 scanVmMetadata 两个 handler(方法名
getVmInstanceMetadata、scanVmMetadata,返回类型 GetVmInstanceMetadataRsp /
ScanVmMetadataRsp)里补齐返回体:不仅记录命令(config.getVmInstanceMetadataCmds /
config.scanVmMetadataCmds),还要把模拟的 metadata 填入响应对象(例如为 GetVmInstanceMetadataRsp
设置 metadata 字段,为 ScanVmMetadataRsp 设置 metadatas/list 字段)并设置必要的状态/错误码为成功,然后通过现有
reply(entity, rsp) 返回;如果有 config 中的预置模拟数据或基于 cmd 的匹配逻辑,优先使用那些数据来填充响应。
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1935-1944: 当前将 metadata 更新按 primary storage 分桶且允许并发 10,会导致同一
VM/根云盘的并发更新冲突;在 ChainTask 的 getSyncSignature() 中不要只用
self.getUuid(),改为包含具体资源标识(例如 msg.getRootVolumeUuid() 或 vmUuid)以把同步粒度收敛到
VM/根云盘,同时将 getSyncLevel() 调整为 1,确保针对同一根云盘的更新串行化(定位: ChainTask 的
getSyncSignature() 和 getSyncLevel() 实现处)。
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 95-96: 在 VmExpungeMetadataFlow.java 中删除多余的 final 变量赋值(即移除 `final
String finalPsUuid = psUuid;` 和 `final String finalHostUuid = hostUuid;`),直接在后续的
lambda/匿名内部类中使用已是 effectively final 的 `psUuid` 和 `hostUuid`。也请检查所有引用
`finalPsUuid` 和 `finalHostUuid` 的位置并改为直接使用 `psUuid` 和
`hostUuid`,确保编译通过并且语义不变(相关符号:psUuid, hostUuid, finalPsUuid, finalHostUuid, 相关
lambda/匿名类)。
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Around line 1336-1346: The code in VmInstanceApiInterceptor should explicitly
check that the PrimaryStorageVO exists before assuming a type; change the lookup
to fetch the whole PrimaryStorageVO (e.g.,
Q.New(PrimaryStorageVO.class).eq(PrimaryStorageVO_.uuid, psUuid).find()) and if
the returned VO is null throw an ApiMessageInterceptionException with a clear
message like "primary storage[uuid:%s] not found"; otherwise read vo.getType()
into psType and then proceed to get the VmMetadataPathBuildExtensionPoint from
pluginRgty and, if missing, throw the existing "does not support vm metadata"
error including the actual non-null type. Ensure you reference
msg.getPrimaryStorageUuid(), PrimaryStorageVO/PrimaryStorageVO_.type,
pluginRgty, VmMetadataPathBuildExtensionPoint, ApiMessageInterceptionException
and argerr when applying the change.
In `@conf/db/upgrade/V5.0.0__schema.sql`:
- Around line 16-24: The VmMetadataFingerprintVO table is missing the audit
timestamp columns present on VmMetadataDirtyVO; update the CREATE TABLE for
VmMetadataFingerprintVO to add the same audit columns (createDate and
lastOpDate) with appropriate TIMESTAMP types and defaults (e.g., createDate
TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP and lastOpDate TIMESTAMP NULL
DEFAULT NULL or with ON UPDATE CURRENT_TIMESTAMP if used in your schema) so the
table matches VmMetadataDirtyVO’s auditing schema while keeping the existing
PRIMARY KEY and FK constraint fkVmMetadataFingerprintVOVmInstanceEO intact.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java`:
- Around line 10-12: Change the numeric wrapper fields in
APICleanupVmInstanceMetadataEvent from Integer to primitive int to follow ZStack
conventions: replace declarations totalCleaned and totalFailed with primitive
int, adjust any corresponding getters/setters and usages to work with int
(initialize defaults if needed), and leave failedVmUuids as List<String>; ensure
no code expects null for these fields.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`:
- Around line 16-17: Rename the ambiguous field uuid in
APIGetVmInstanceMetadataFromPrimaryStorageMsg to vmInstanceUuid: change the
private field name, its getter/setter, and any usages to vmInstanceUuid, keep
the `@APIParam`(resourceType = VmInstanceVO.class) annotation on the field, and
ensure the class implements/overrides VmInstanceMessage.getVmInstanceUuid() (and
update any callers or marshalling logic that referenced the old uuid symbol) so
names remain consistent across the codebase.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java`:
- Around line 30-33: The __example__() method sets vmUuids by assigning the
field directly (msg.vmUuids = ...) which is inconsistent with the rest of the
class; update __example__() to use the setter instead: call
msg.setVmUuids(Collections.singletonList(uuid())) so the example follows the
class's accessor pattern and any validation/logic in setVmUuids is applied.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java`:
- Around line 18-22: Make the private field code in the VmMetadataErrors enum
final and update the constructor to format the integer id with %d instead of %s;
specifically change the declaration of code to "private final String code" and
update the VmMetadataErrors(int id) constructor to use
String.format("VM_METADATA.%d", id) so code is immutable and formatted
correctly.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java`:
- Around line 3-8: Add Javadoc comments for every method in the
VmMetadataPathBuildExtensionPoint interface: document getPrimaryStorageType(),
buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid),
buildMetadataDir(String primaryStorageUuid), and especially
validateMetadataPath(String primaryStorageUuid, String path) — explicitly state
the return semantics (e.g. returns normalized/validated path on success, or
null/throws/returns error message on failure) and describe parameters and
expected behavior; keep methods without explicit modifiers (interface methods
remain implicitly public) and place the Javadoc directly above each method
declaration.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 27-41: The batchResolveVmUuids method currently accumulates
resolved UUIDs into a List allowing duplicates; change the aggregation to
preserve insertion order and remove duplicates by collecting results into a
LinkedHashSet (or equivalent) while iterating over fieldValues and
resolveVmUuids(v), then return a List built from that set; reference
batchResolveVmUuids and resolveVmUuids to locate the code and ensure null checks
and skipping null fieldValues remain unchanged.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 944-952: Remove the redundant outer-class qualifiers when
declaring the inner classes: change the extends clauses of
PrefixRebaseBackingFilesCmd and PrefixRebaseBackingFilesRsp to extend
AgentCommand and AgentResponse (respectively) instead of
LocalStorageKvmBackend.AgentCommand and LocalStorageKvmBackend.AgentResponse so
the inner classes use the enclosing class's types directly; update only the
class declarations for PrefixRebaseBackingFilesCmd and
PrefixRebaseBackingFilesRsp.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 24-146: Extract the duplicated logic in calculatePathReplacements,
validateMetadataPath, buildMetadataDir/buildVmMetadataPath string construction,
normalizeBaseDir and createResourceRef into a new abstract base class (e.g.,
AbstractVmMetadataExtension) that implements VmMetadataPathBuildExtensionPoint,
VmMetadataPathReplacementExtensionPoint and
VmMetadataResourcePersistExtensionPoint; add an abstract helper method like
getBaseDir(String primaryStorageUuid) that subclasses implement
(LocalStorageVmMetadataExtension returns PrimaryStorageVO_.url while
NfsVmMetadataExtension returns mountPath or url as needed), move shared
constants use (VmInstanceMetadataConstants) and path normalization there, keep
DB persistence (dbf.persist) either in the base createResourceRef or callable
from base, and update LocalStorageVmMetadataExtension and NfsVmMetadataExtension
to extend the new base and only implement the storage-specific base-dir
retrieval and any small differences.
- Around line 37-53: Both buildMetadataDir and buildVmMetadataPath duplicate the
PrimaryStorageVO.url query; extract a private helper (e.g., getNormalizedBaseDir
or fetchNormalizedUrl) that runs the
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue(), throws the same CloudRuntimeException if null,
and returns normalizeBaseDir(url); then have buildMetadataDir call that helper
and append VmInstanceMetadataConstants.METADATA_DIR_NAME, and have
buildVmMetadataPath call the helper and build the full path with
VmInstanceMetadataConstants.METADATA_DIR_NAME and
VmInstanceMetadataConstants.FILE_METADATA_SUFFIX to remove the duplicated query
logic.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java`:
- Around line 96-104: Add Javadoc to each of the five interface methods
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg...),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg...),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg...),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...),
handle(RebaseVolumeBackingFileOnPrimaryStorageMsg...)) that documents: (1)
hostUuid is required (non-null) and must be the UUID of a host that is connected
to the primary storage and where the operation will be executed (caller is
responsible for selecting such a host); (2) what the method does at a high
level; and (3) the failure contract — implementations must report errors via the
provided ReturnValueCompletion (e.g., completion.fail on any local/remote
failure) and must not throw unchecked exceptions. Ensure the Javadoc follows the
project style and add no extra modifiers to the interface methods.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 80-89: The code in calculatePathReplacements duplicates baseDir
resolution (checking mountPath then url); replace that block by calling
resolveBaseDir(targetPsUuid) and handle any exception it throws; if
resolveBaseDir returns null behave as before (logger.warn(...) and return a
PathReplacementResult with empty metadata map), and if it throws, log the error
and return the same empty PathReplacementResult—update references inside
calculatePathReplacements to use the resolved value and preserve existing
variables (targetPsUuid, PathReplacementResult) and logging.
In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java`:
- Around line 22-27: The field and its accessor signatures in
CleanupVmInstanceMetadataResult should use a generic type to avoid raw List
usage: change the field failedVmUuids and its setter/getter signatures from
java.util.List to java.util.List<String> (update the field declaration,
setFailedVmUuids(List<String> failedVmUuids), and getFailedVmUuids():
List<String>) and regenerate/compile; also apply the same change for
ScanVmInstanceMetadataFromPrimaryStorageResult.vmInstanceMetadata to use a typed
List (e.g., List<String> or the appropriate element type) in the codegen
template so callers don't need unchecked casts.
In `@sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java`:
- Around line 79-85: Rename the boolean getter in class VmMetadataScanEntry from
getIncomplete() to isIncomplete() to follow JavaBean conventions: keep the field
incomplete and the setter setIncomplete(boolean), add or replace the existing
getIncomplete() method with a public boolean isIncomplete() method, and update
any references/call sites to use isIncomplete() (or keep getIncomplete() as a
deprecated wrapper if backward compatibility is required).
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1863-1864: Replace the extra DB query that obtains psType with the
instance's stored type: use self.getType() (from PrimaryStorageBase) instead of
calling Q.New(PrimaryStorageVO.class)...findValue(); then pass that type into
pluginRgty.getExtensionFromMap(..., VmMetadataPathBuildExtensionPoint.class). In
other words, remove the Q.New(...) lookup and call self.getType() (or
this.getType()) when resolving the extension in PrimaryStorageBase so
pluginRgty.getExtensionFromMap uses the in-memory type.
🪄 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: 8fd4ef01-f15c-4f0d-a75f-13eb2986a26d
⛔ 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 (144)
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/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/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/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/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 getPrimaryStorageType(); | ||
| PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths); | ||
| class PathReplacementResult { |
There was a problem hiding this comment.
扩展点契约建议补充方法级 Javadoc。
这里是跨主存储实现的关键扩展接口,建议明确输入输出语义,避免实现行为不一致。
建议补丁
public interface VmMetadataPathReplacementExtensionPoint {
+ /**
+ * `@return` 当前扩展支持的主存储类型
+ */
String getPrimaryStorageType();
+
+ /**
+ * 计算历史元数据路径到当前路径的替换关系。
+ */
PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths);As per coding guidelines, “接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。”
📝 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.
| String getPrimaryStorageType(); | |
| PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths); | |
| class PathReplacementResult { | |
| /** | |
| * `@return` 当前扩展支持的主存储类型 | |
| */ | |
| String getPrimaryStorageType(); | |
| /** | |
| * 计算历史元数据路径到当前路径的替换关系。 | |
| */ | |
| PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths); | |
| class PathReplacementResult { |
🤖 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 8 - 10, Add clear method-level Javadoc to the
VmMetadataPathReplacementExtensionPoint interface: document
getPrimaryStorageType() to specify the expected returned storage type string and
whether null/empty is allowed; document calculatePathReplacements(String
targetPsUuid, List<String> allOldPaths) to describe semantics of targetPsUuid
and allOldPaths (non-null, expected format), what the implementation must
compute, concurrency expectations, error handling and whether it may throw
exceptions or should return an empty PathReplacementResult on no changes; and
document the nested PathReplacementResult class to explain fields, meaning of
empty vs null collections, and how callers should interpret the result.
Reference the methods getPrimaryStorageType(), calculatePathReplacements(...),
and the PathReplacementResult class when adding the Javadoc so implementors have
unambiguous input/output contracts.
| String getPrimaryStorageType(); | ||
| void afterVolumePersist(String primaryStorageUuid, String resourceUuid, | ||
| String resourceType, String hostUuid, long size, Timestamp now); | ||
| void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid, | ||
| String resourceType, String hostUuid, long size, Timestamp now); |
There was a problem hiding this comment.
请为扩展点接口方法补充 Javadoc。
当前接口方法缺少说明,后续实现方难以统一参数语义与调用时机。建议在接口层补齐简明契约注释。
建议补丁
public interface VmMetadataResourcePersistExtensionPoint {
+ /**
+ * `@return` 当前扩展支持的主存储类型
+ */
String getPrimaryStorageType();
+
+ /**
+ * 卷元数据落库后的回调。
+ */
void afterVolumePersist(String primaryStorageUuid, String resourceUuid,
String resourceType, String hostUuid, long size, Timestamp now);
+
+ /**
+ * 快照元数据落库后的回调。
+ */
void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid,
String resourceType, String hostUuid, long size, Timestamp now);
}As per coding guidelines, “接口方法不应有多余的修饰符(例如 public),且必须配有有效的 Javadoc 注释。”
📝 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.
| String getPrimaryStorageType(); | |
| void afterVolumePersist(String primaryStorageUuid, String resourceUuid, | |
| String resourceType, String hostUuid, long size, Timestamp now); | |
| void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid, | |
| String resourceType, String hostUuid, long size, Timestamp now); | |
| public interface VmMetadataResourcePersistExtensionPoint { | |
| /** | |
| * `@return` 当前扩展支持的主存储类型 | |
| */ | |
| String getPrimaryStorageType(); | |
| /** | |
| * 卷元数据落库后的回调。 | |
| */ | |
| void afterVolumePersist(String primaryStorageUuid, String resourceUuid, | |
| String resourceType, String hostUuid, long size, Timestamp now); | |
| /** | |
| * 快照元数据落库后的回调。 | |
| */ | |
| void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid, | |
| String resourceType, String hostUuid, long size, Timestamp now); | |
| } |
🤖 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/VmMetadataResourcePersistExtensionPoint.java`
around lines 6 - 10, The interface VmMetadataResourcePersistExtensionPoint and
its methods getPrimaryStorageType, afterVolumePersist, and afterSnapshotPersist
lack Javadoc; add concise Javadoc for each method at the interface level (no
extra modifiers) describing purpose, caller contract, exact parameter semantics
(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now), expected
return/side-effects, and when the method is invoked (e.g., after a volume or
snapshot is persisted to primary storage), plus any threading or exception
expectations so implementers have a clear, uniform contract.
| thdf.chainSubmit(new ChainTask(msg) { | ||
| @Override | ||
| public String getSyncSignature() { | ||
| return "update-metadata-on-ps-" + self.getUuid(); | ||
| } | ||
|
|
||
| @Override | ||
| public int getSyncLevel() { | ||
| return 10; | ||
| } |
There was a problem hiding this comment.
不要在同一块 local primary storage 上并发 10 个 metadata 写入。
当前同步签名只包含 self.getUuid(),而 getSyncLevel() 放开到 10;同一 VM 的 metadata 更新会并发执行。对这种直接落文件的路径来说,这很容易把后一条更新覆盖前一条。建议按 rootVolumeUuid/vmUuid 分桶,或者保持单并发。
💡 建议修改
`@Override`
public String getSyncSignature() {
- return "update-metadata-on-ps-" + self.getUuid();
+ return "update-metadata-on-ps-" + self.getUuid() + "-root-volume-" + msg.getRootVolumeUuid();
}
`@Override`
public int getSyncLevel() {
- return 10;
+ return 1;
}🤖 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 3358 - 3367, The ChainTask submitted via thdf.chainSubmit uses
getSyncSignature() == "update-metadata-on-ps-" + self.getUuid() but
getSyncLevel() returns 10, which allows up to 10 concurrent metadata writes for
the same local primary storage and can cause race/overwrite on file-based
metadata; change the synchronization to serialize by either returning a
per-VM/per-root-volume signature (e.g. include rootVolumeUuid or vmUuid in
getSyncSignature()) or reduce getSyncLevel() to 1 to force single concurrency;
update the anonymous ChainTask in LocalStorageBase (the thdf.chainSubmit block
implementing getSyncSignature and getSyncLevel) accordingly so same
VM/rootVolume operations are bucketed or fully serialized.
| String hostUuid = null; | ||
| if (msg.getHostUuid() != null) { | ||
| hostUuid = msg.getHostUuid(); | ||
| } | ||
|
|
||
| if (hostUuid == null) { | ||
| if (msg.getRootVolumeUuid() == null) { | ||
| reply.setError(operr("cannot determine host for vm metadata get on local primary storage[uuid:%s]," + | ||
| " rootVolumeUuid is null", self.getUuid())); | ||
| bus.reply(msg, reply); | ||
| return; | ||
| } | ||
| try { | ||
| hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); | ||
| } catch (Exception e) { | ||
| reply.setError(operr("cannot determine host for vm metadata get on local primary storage[uuid:%s], rootVolumeUuid[%s]: %s", | ||
| self.getUuid(), msg.getRootVolumeUuid(), e.getMessage())); | ||
| bus.reply(msg, reply); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); | ||
| LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); | ||
| bkd.handle(msg, hostUuid, new ReturnValueCompletion<GetVmInstanceMetadataFromPrimaryStorageReply>(msg) { |
There was a problem hiding this comment.
把“传入的 hostUuid 已失效”当成单独分支处理。
GetVmInstanceMetadataFromPrimaryStorageMsg 里只在 hostUuid == null 时才回退到 rootVolumeUuid;卷迁移后如果调用方还带着旧 hostUuid,这里会直接失败,明明还能从当前 root volume 重新解析到新主机。反过来,CleanupVmInstanceMetadataOnPrimaryStorageMsg 如果持久化的是旧 hostUuid,又不能简单回退到当前 root-volume host——那会清到错误的主机;对“主机已删除/已解绑”这种终态更合适的行为是直接视为清理完成或取消 GC。现在这两条路径都把失效 hostUuid 当普通错误处理,前者会误失败,后者会把 GC 变成持续重试。
Also applies to: 3509-3527
🤖 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 3413 - 3437, The current flow treats a supplied but no-longer-valid
hostUuid as an ordinary error; update LocalStorageBase to (1) for
GetVmInstanceMetadataFromPrimaryStorageMsg: if msg.getHostUuid() is non-null but
lookup of host or hypervisor backend indicates the host is
deleted/unbound/invalid, fall back to resolving host via
getHostUuidByResourceUuid(msg.getRootVolumeUuid()) and proceed with the backend
from getHypervisorBackendFactoryByHostUuid(resolvedHostUuid) before failing; (2)
for CleanupVmInstanceMetadataOnPrimaryStorageMsg: if the persisted hostUuid is
invalid/terminal (host deleted or detached), consider the cleanup a no-op
success (or cancel GC) instead of retrying—detect this via the same host
lookup/validation and return success to the bus; change error branches around
getHostUuidByResourceUuid, getHypervisorBackendFactoryByHostUuid, and bkd.handle
to implement these two behaviors.
| @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; |
There was a problem hiding this comment.
补齐 simulator 的读取/扫描返回体。
这两个 handler 现在只记录 command,没有把任何 metadata 内容写回 GetVmInstanceMetadataRsp / ScanVmMetadataRsp。这样在 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 346 - 363, 在 getVmInstanceMetadata 和 scanVmMetadata 两个 handler(方法名
getVmInstanceMetadata、scanVmMetadata,返回类型 GetVmInstanceMetadataRsp /
ScanVmMetadataRsp)里补齐返回体:不仅记录命令(config.getVmInstanceMetadataCmds /
config.scanVmMetadataCmds),还要把模拟的 metadata 填入响应对象(例如为 GetVmInstanceMetadataRsp
设置 metadata 字段,为 ScanVmMetadataRsp 设置 metadatas/list 字段)并设置必要的状态/错误码为成功,然后通过现有
reply(entity, rsp) 返回;如果有 config 中的预置模拟数据或基于 cmd 的匹配逻辑,优先使用那些数据来填充响应。
| thdf.chainSubmit(new ChainTask(msg) { | ||
| @Override | ||
| public String getSyncSignature() { | ||
| return "update-metadata-on-ps-" + self.getUuid(); | ||
| } | ||
|
|
||
| @Override | ||
| public int getSyncLevel() { | ||
| return 10; | ||
| } |
There was a problem hiding this comment.
把 metadata 更新的同步粒度收敛到 VM/根云盘。
这里按 primary storage 分桶、但允许 10 并发;同一台 VM 的两次 metadata 更新会并行落到同一个后端写路径上。只要底层是覆盖写同一份 metadata 文件,就会出现丢更新/写出半成品的竞争。建议把 getSyncSignature() 至少细化到 msg.getRootVolumeUuid()(或 vmUuid),并把 getSyncLevel() 收到 1。
💡 建议修改
`@Override`
public String getSyncSignature() {
- return "update-metadata-on-ps-" + self.getUuid();
+ return "update-metadata-on-ps-" + self.getUuid() + "-root-volume-" + msg.getRootVolumeUuid();
}
`@Override`
public int getSyncLevel() {
- return 10;
+ return 1;
}🤖 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 1935 - 1944, 当前将 metadata 更新按 primary storage 分桶且允许并发 10,会导致同一
VM/根云盘的并发更新冲突;在 ChainTask 的 getSyncSignature() 中不要只用
self.getUuid(),改为包含具体资源标识(例如 msg.getRootVolumeUuid() 或 vmUuid)以把同步粒度收敛到
VM/根云盘,同时将 getSyncLevel() 调整为 1,确保针对同一根云盘的更新串行化(定位: ChainTask 的
getSyncSignature() 和 getSyncLevel() 实现处)。
da8ac3e to
208c773
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1935-1944:⚠️ Potential issue | 🔴 Critical把 metadata update 的同步粒度收敛到 VM/根云盘。
这个点在前一个 patch set 已提过,当前实现仍然只按 primary storage 分桶且允许 10 并发。同一台 VM/同一根云盘的并发 update 仍会同时改写同一个 metadata 文件,结果是非确定的。
getSyncSignature()至少要带上msg.getRootVolumeUuid()或msg.getVmUuid(),getSyncLevel()也应该收敛到 1。🤖 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 1935 - 1944, The current ChainTask submitted via thdf.chainSubmit uses getSyncSignature() = "update-metadata-on-ps-" + self.getUuid() and getSyncLevel() = 10 which buckets only by primary storage; change getSyncSignature() to include the target VM/root volume identifier (e.g., append msg.getVmUuid() or msg.getRootVolumeUuid() to "update-metadata-on-ps-"+self.getUuid()) and reduce getSyncLevel() to 1 so updates serialize per VM/root volume; update the ChainTask implementation accordingly (refer to thdf.chainSubmit, ChainTask, getSyncSignature(), getSyncLevel(), self.getUuid(), msg.getVmUuid(), msg.getRootVolumeUuid()).plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (2)
3358-3367:⚠️ Potential issue | 🔴 Critical不要把同一 local PS 上的 metadata 更新放到 10 并发。
现在的同步签名只按
self.getUuid()分桶,但getSyncLevel()放开到了 10;同一 VM/root volume 的 metadata 写入仍然会并发落到同一存储路径上,后到请求可以覆盖先到结果。可选修正
`@Override` public String getSyncSignature() { - return "update-metadata-on-ps-" + self.getUuid(); + return "update-metadata-on-ps-" + self.getUuid() + "-root-volume-" + msg.getRootVolumeUuid(); } `@Override` public int getSyncLevel() { - return 10; + return 1; }🤖 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 3358 - 3367, The current ChainTask submitted via thdf.chainSubmit uses getSyncSignature() returning "update-metadata-on-ps-"+self.getUuid() but getSyncLevel()=10, allowing concurrent metadata writes to the same storage path; fix by making the sync key unique per storage path or volume (e.g., include the target storage path/volume identifier in getSyncSignature() instead of only self.getUuid(), or alternatively change getSyncLevel() to 1) so metadata updates for the same local PS/storage path are serialized; update the anonymous ChainTask implementations of getSyncSignature() / getSyncLevel() accordingly.
3413-3437:⚠️ Potential issue | 🟠 Major把“传入的 hostUuid 已失效”当成单独分支处理。
Get...这里仍然只有在hostUuid == null时才回退到rootVolumeUuid;卷迁移后如果调用方带着旧 hostUuid,会直接在 backend factory 解析处失败,本来还能从当前 root volume 重新定位。Cleanup...也一样:对已删除/已解绑的宿主机继续返回 error,会把 GC 拉成持续重试;这种终态更适合视为 no-op 成功或直接取消 GC。Also applies to: 3509-3527
🤖 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 3413 - 3437, The code only falls back to rootVolumeUuid when msg.getHostUuid() is null, so when callers pass a stale/invalid hostUuid the call fails at getHypervisorBackendFactoryByHostUuid and misses an opportunity to re-resolve from the root volume; change the flow in methods handling GetVmInstanceMetadataFromPrimaryStorage* and Cleanup* so that: read hostUuid = msg.getHostUuid(); if hostUuid != null attempt getHypervisorBackendFactoryByHostUuid(hostUuid) inside a try; if that fails, call getHostUuidByResourceUuid(msg.getRootVolumeUuid()) and retry resolving the factory; if resolution still fails return the original error for non-cleanup operations, but for cleanup/GC paths treat “host not found/unbound” as terminal and succeed/no-op (or cancel GC) instead of forcing retries; use symbols getHostUuidByResourceUuid, getHypervisorBackendFactoryByHostUuid, bkd.handle, and the GetVmInstanceMetadataFromPrimaryStorage*/Cleanup* handlers to implement this fallback and terminal-success 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/VmExpungeMetadataFlow.java`:
- Around line 89-123: The code currently forwards a potentially null hostUuid
from VmExpungeMetadataFlow into submitGC which makes GC retries unroutable;
change the logic so we don't hard-set or propagate a null host: update
VmExpungeMetadataFlow (where hostUuid is derived and submitGC(...) is called) to
pass a nullable host marker (leave the parameter null) only when no host is
available, and modify submitGC (and the
CleanupVmInstanceMetadataOnPrimaryStorageGC fields assignment) to avoid
assigning gc.hostUuid when the incoming hostUuid is null and instead defer host
resolution to the GC execution/handler (implement host selection there by
looking up an available host for the primary storage psUuid or using
vm.lastHostUuid as fallback). Ensure symbols involved are VmExpungeMetadataFlow,
submitGC, and CleanupVmInstanceMetadataOnPrimaryStorageGC so the host selection
is performed at GC runtime rather than propagating null now.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 2039-2060: The cleanup handler for
CleanupVmInstanceMetadataOnPrimaryStorageMsg currently calls backend.handle(...)
directly, bypassing the existing ChainTask-based serialization used for
updates/registrations and risking races that delete or interrupt concurrent
writes; change handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg) to submit
the cleanup as a ChainTask (same serialization path as the update/register flow)
using the same sync signature key (derive it from rootVolumeUuid or
metadataPath) so cleanup runs serialized with other metadata operations, and
inside the task invoke backend.handle(...) with the existing
ReturnValueCompletion to reply via bus.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2157-2162: The reply construction in NfsPrimaryStorageKVMBackend
(the block creating ScanVmInstanceMetadataFromPrimaryStorageReply r) returns
null when rsp.metadataEntries is null; update the code so
r.setVmInstanceMetadata(...) always receives a non-null list (use
Collections.emptyList() when rsp.metadataEntries == null), preserve the existing
hostUuid-setting logic for non-null entries, and add the necessary import for
java.util.Collections if missing, so callers never receive a null metadata list.
- Around line 2168-2177: 在
NfsPrimaryStorageKVMBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...)
中不要直接用传入的 hostUuid 路由 KVMHostAsyncHttpCallMsg(hmsg),因为在 expunge/GC 场景 hostUuid
可能为空或不可用;改为当 msg.getHostUuid() 为 null 或该主机不可达时,查找任一已连接并挂载该 NFS
的可用主机(例如通过现有的主机查询/挂载检查方法、主机状态过滤器或已有的 findHostsByPrimaryStorageUuid/QueryHost
APIs),将找到的主机 UUID 赋给 hmsg.setHostUuid(...),并用该 UUID 调用
bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID,
selectedHostUuid);若没有可用主机则按原有逻辑通过 completion 返回失败。确保引用
CleanupVmInstanceMetadataOnPrimaryStorageMsg、CleanupVmMetadataCmd、KVMHostAsyncHttpCallMsg、handle
和 bus.makeTargetServiceIdByResourceUuid 等标识以便定位修改点。
---
Duplicate comments:
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3358-3367: The current ChainTask submitted via thdf.chainSubmit
uses getSyncSignature() returning "update-metadata-on-ps-"+self.getUuid() but
getSyncLevel()=10, allowing concurrent metadata writes to the same storage path;
fix by making the sync key unique per storage path or volume (e.g., include the
target storage path/volume identifier in getSyncSignature() instead of only
self.getUuid(), or alternatively change getSyncLevel() to 1) so metadata updates
for the same local PS/storage path are serialized; update the anonymous
ChainTask implementations of getSyncSignature() / getSyncLevel() accordingly.
- Around line 3413-3437: The code only falls back to rootVolumeUuid when
msg.getHostUuid() is null, so when callers pass a stale/invalid hostUuid the
call fails at getHypervisorBackendFactoryByHostUuid and misses an opportunity to
re-resolve from the root volume; change the flow in methods handling
GetVmInstanceMetadataFromPrimaryStorage* and Cleanup* so that: read hostUuid =
msg.getHostUuid(); if hostUuid != null attempt
getHypervisorBackendFactoryByHostUuid(hostUuid) inside a try; if that fails,
call getHostUuidByResourceUuid(msg.getRootVolumeUuid()) and retry resolving the
factory; if resolution still fails return the original error for non-cleanup
operations, but for cleanup/GC paths treat “host not found/unbound” as terminal
and succeed/no-op (or cancel GC) instead of forcing retries; use symbols
getHostUuidByResourceUuid, getHypervisorBackendFactoryByHostUuid, bkd.handle,
and the GetVmInstanceMetadataFromPrimaryStorage*/Cleanup* handlers to implement
this fallback and terminal-success behavior.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1935-1944: The current ChainTask submitted via thdf.chainSubmit
uses getSyncSignature() = "update-metadata-on-ps-" + self.getUuid() and
getSyncLevel() = 10 which buckets only by primary storage; change
getSyncSignature() to include the target VM/root volume identifier (e.g., append
msg.getVmUuid() or msg.getRootVolumeUuid() to
"update-metadata-on-ps-"+self.getUuid()) and reduce getSyncLevel() to 1 so
updates serialize per VM/root volume; update the ChainTask implementation
accordingly (refer to thdf.chainSubmit, ChainTask, getSyncSignature(),
getSyncLevel(), self.getUuid(), msg.getVmUuid(), msg.getRootVolumeUuid()).
🪄 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: cf42664d-a552-4049-94db-8856fd19b240
⛔ 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 (144)
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/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/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/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/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 (82)
- 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/vm/APISetVmBootOrderMsg.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- 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/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
- sdk/src/main/java/SourceClassMap.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
- storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
🚧 Files skipped from review as they are similar to previous changes (45)
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/VmInstanceState.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.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/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
| String hostUuid = spec.getVmInventory().getHostUuid(); | ||
| if (hostUuid == null) { | ||
| hostUuid = spec.getVmInventory().getLastHostUuid(); | ||
| } | ||
| cmsg.setHostUuid(hostUuid); | ||
|
|
||
| final String finalPsUuid = psUuid; | ||
| final String finalHostUuid = hostUuid; | ||
|
|
||
| bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); | ||
| bus.send(cmsg, new CloudBusCallBack(trigger) { | ||
| @Override | ||
| public void run(MessageReply reply) { | ||
| if (reply.isSuccess()) { | ||
| logger.info(String.format("[MetadataExpunge] successfully deleted metadata for vm[uuid:%s] on ps[uuid:%s]", | ||
| vmUuid, finalPsUuid)); | ||
| } else { | ||
| logger.warn(String.format("[MetadataExpunge] failed to delete metadata for vm[uuid:%s] on ps[uuid:%s]: %s, " + | ||
| "submitting GC job for retry", vmUuid, finalPsUuid, reply.getError())); | ||
| submitGC(finalPsUuid, vmUuid, rootVolumeUuid, metadataPath, finalHostUuid); | ||
| } | ||
| trigger.next(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private void submitGC(String psUuid, String vmUuid, String rootVolumeUuid, String metadataPath, String hostUuid) { | ||
| CleanupVmInstanceMetadataOnPrimaryStorageGC gc = new CleanupVmInstanceMetadataOnPrimaryStorageGC(); | ||
| gc.NAME = CleanupVmInstanceMetadataOnPrimaryStorageGC.getGCName(vmUuid); | ||
| gc.primaryStorageUuid = psUuid; | ||
| gc.vmUuid = vmUuid; | ||
| gc.rootVolumeUuid = rootVolumeUuid; | ||
| gc.metadataPath = metadataPath; | ||
| gc.hostUuid = hostUuid; | ||
| long gcIntervalSec = TimeUnit.HOURS.toSeconds(VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class)); |
There was a problem hiding this comment.
不要把 null 的 hostUuid 继续传给 GC。
expunge 场景下 hostUuid/lastHostUuid 可能都拿不到,这里仍然把该值透传给 submitGC()。这样初次 cleanup 失败后,GC 重试也没有可路由的宿主机,只会重复失败。这里至少要补一个可用 host 的兜底,或者把 host 选择延后到 handler/GC 执行时再决定。
🤖 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 - 123, The code currently forwards a potentially null hostUuid
from VmExpungeMetadataFlow into submitGC which makes GC retries unroutable;
change the logic so we don't hard-set or propagate a null host: update
VmExpungeMetadataFlow (where hostUuid is derived and submitGC(...) is called) to
pass a nullable host marker (leave the parameter null) only when no host is
available, and modify submitGC (and the
CleanupVmInstanceMetadataOnPrimaryStorageGC fields assignment) to avoid
assigning gc.hostUuid when the incoming hostUuid is null and instead defer host
resolution to the GC execution/handler (implement host selection there by
looking up an available host for the primary storage psUuid or using
vm.lastHostUuid as fallback). Ensure symbols involved are VmExpungeMetadataFlow,
submitGC, and CleanupVmInstanceMetadataOnPrimaryStorageGC so the host selection
is performed at GC runtime rather than propagating null now.
| protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); | ||
| 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(); | ||
| final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); | ||
| backend.handle(msg, hostUuid, new ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>(msg) { | ||
| @Override | ||
| public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply r) { | ||
| bus.reply(msg, r); | ||
| } | ||
|
|
||
| @Override | ||
| public void fail(ErrorCode errorCode) { | ||
| reply.setError(errorCode); | ||
| bus.reply(msg, reply); | ||
| } | ||
| }); |
There was a problem hiding this comment.
让 cleanup 和 update 走同一条串行化通道。
CleanupVmInstanceMetadataOnPrimaryStorageMsg 这里直接下发 backend,绕过了 Line 1935-1944 的 ChainTask。如果 expunge cleanup 与 update/register 并发命中同一个 metadataPath,cleanup 可能删掉刚写好的文件或打断写入。建议复用同一个 sync signature(至少按 rootVolumeUuid 或 metadataPath)串行化 cleanup。
🤖 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 2039 - 2060, The cleanup handler for
CleanupVmInstanceMetadataOnPrimaryStorageMsg currently calls backend.handle(...)
directly, bypassing the existing ChainTask-based serialization used for
updates/registrations and risking races that delete or interrupt concurrent
writes; change handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg) to submit
the cleanup as a ChainTask (same serialization path as the update/register flow)
using the same sync signature key (derive it from rootVolumeUuid or
metadataPath) so cleanup runs serialized with other metadata operations, and
inside the task invoke backend.handle(...) with the existing
ReturnValueCompletion to reply via bus.
| 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.
为 NFS 清理路径补上可用主机回退。
Line 2176-2177 直接用入参 hostUuid 路由消息。expunge/GC 场景里这个值可能为空或已经不可用,但 NFS 上的元数据文件本身是共享存储内容,本来可以改由任一已连接且已挂载该 NFS 的主机执行清理。现在这里会把一次本可成功的清理直接变成消息路由失败。
💡 建议修改
public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.setUuid(msg.getPrimaryStorageUuid());
cmd.metadataPath = msg.getMetadataPath();
+ String targetHostUuid = hostUuid;
+ if (targetHostUuid == null ||
+ !Q.New(HostVO.class).eq(HostVO_.uuid, targetHostUuid).eq(HostVO_.status, HostStatus.Connected).isExists()) {
+ PrimaryStorageVO psvo = dbf.findByUuid(msg.getPrimaryStorageUuid(), PrimaryStorageVO.class);
+ if (psvo == null) {
+ completion.fail(operr("cannot find nfs primary storage[uuid:%s]", msg.getPrimaryStorageUuid()));
+ return;
+ }
+ targetHostUuid = nfsFactory.getConnectedHostForOperation(PrimaryStorageInventory.valueOf(psvo)).get(0).getUuid();
+ }
+
KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
hmsg.setCommand(cmd);
hmsg.setPath(CLEANUP_VM_METADATA_PATH);
- hmsg.setHostUuid(hostUuid);
- bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid);
+ hmsg.setHostUuid(targetHostUuid);
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, targetHostUuid);🤖 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, 在
NfsPrimaryStorageKVMBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...)
中不要直接用传入的 hostUuid 路由 KVMHostAsyncHttpCallMsg(hmsg),因为在 expunge/GC 场景 hostUuid
可能为空或不可用;改为当 msg.getHostUuid() 为 null 或该主机不可达时,查找任一已连接并挂载该 NFS
的可用主机(例如通过现有的主机查询/挂载检查方法、主机状态过滤器或已有的 findHostsByPrimaryStorageUuid/QueryHost
APIs),将找到的主机 UUID 赋给 hmsg.setHostUuid(...),并用该 UUID 调用
bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID,
selectedHostUuid);若没有可用主机则按原有逻辑通过 completion 返回失败。确保引用
CleanupVmInstanceMetadataOnPrimaryStorageMsg、CleanupVmMetadataCmd、KVMHostAsyncHttpCallMsg、handle
和 bus.makeTargetServiceIdByResourceUuid 等标识以便定位修改点。
300aab9 to
5f516f9
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (4)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2173-2177:⚠️ Potential issue | 🔴 Critical清理路由不要直接依赖传入的
hostUuid。expunge/GC 场景里这个值可能为空,或者对应主机已经不可用;这里直接拿它做
setHostUuid(...)和makeTargetServiceIdByResourceUuid(...),会把本来可以在任意已挂载 NFS 的可用主机上完成的清理,变成消息路由失败。建议在为空或不可用时回退到nfsFactory.getConnectedHostForOperation(...)选出的主机。💡 建议修改
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); cmd.setUuid(msg.getPrimaryStorageUuid()); cmd.metadataPath = msg.getMetadataPath(); + String targetHostUuid = hostUuid; + if (targetHostUuid == null || !Q.New(HostVO.class) + .eq(HostVO_.uuid, targetHostUuid) + .eq(HostVO_.status, HostStatus.Connected) + .isExists()) { + PrimaryStorageVO ps = dbf.findByUuid(msg.getPrimaryStorageUuid(), PrimaryStorageVO.class); + if (ps == null) { + completion.fail(operr("cannot find nfs primary storage[uuid:%s]", msg.getPrimaryStorageUuid())); + return; + } + + List<HostInventory> hosts = nfsFactory.getConnectedHostForOperation(PrimaryStorageInventory.valueOf(ps)); + if (hosts.isEmpty()) { + completion.fail(operr("no connected host found for nfs primary storage[uuid:%s]", msg.getPrimaryStorageUuid())); + return; + } + + targetHostUuid = hosts.get(0).getUuid(); + } + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); hmsg.setCommand(cmd); hmsg.setPath(CLEANUP_VM_METADATA_PATH); - hmsg.setHostUuid(hostUuid); - bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + hmsg.setHostUuid(targetHostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, targetHostUuid);🤖 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 2173 - 2177, The code currently uses the incoming hostUuid directly when building the KVMHostAsyncHttpCallMsg (KVMHostAsyncHttpCallMsg, setHostUuid, makeTargetServiceIdByResourceUuid), which breaks GC/expunge flows if hostUuid is null or the host is unavailable; change the logic to validate the incoming hostUuid and if it is null or not reachable, call nfsFactory.getConnectedHostForOperation(...) to select a usable connected host, set that host's UUID on the message via setHostUuid(...), and then call bus.makeTargetServiceIdByResourceUuid(...) with the selected host UUID so the cleanup can be routed to any available NFS-mounted host.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (2)
2039-2060:⚠️ Potential issue | 🔴 Criticalcleanup 需要和 update 走同一条串行化通道。
这里直接下发 backend,仍然绕过了上面的
ChainTask。如果 expunge cleanup 和 register/update 并发命中同一个 metadataPath,cleanup 可能删掉刚写好的文件或打断写入。建议复用 update 的 sync signature(至少按rootVolumeUuid或 metadataPath)把 cleanup 串起来。🤖 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 2039 - 2060, The cleanup handler currently calls backend.handle(...) directly (in handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) and bypasses the ChainTask serialization used by update, risking race with register/update; change this to enqueue the cleanup as a ChainTask using the same sync signature logic as the update path (serialize by rootVolumeUuid or metadataPath) so cleanup runs on the same serialized channel; locate the handler that builds ChainTask for update (the update/register path that composes the sync signature) and reuse that signature and queuing pattern instead of calling backend.handle directly, then inside the ChainTask invoke the backend cleanup and reply with the existing CleanupVmInstanceMetadataOnPrimaryStorageReply on success/failure.
1935-1944:⚠️ Potential issue | 🔴 Critical把 metadata 更新的串行化粒度收敛到 VM/根云盘。
现在的 sync key 只有 primary storage,而且
getSyncLevel()允许 10 并发。同一台 VM 的两次 metadata 更新会并行落到同一路径上,覆盖写时很容易丢更新或写出半成品。这里至少要把 key 带上msg.getRootVolumeUuid()(或 metadataPath),并把并发收敛到 1。💡 建议修改
`@Override` public String getSyncSignature() { - return "update-metadata-on-ps-" + self.getUuid(); + return "update-metadata-on-ps-" + self.getUuid() + "-root-volume-" + msg.getRootVolumeUuid(); } `@Override` public int getSyncLevel() { - return 10; + return 1; }🤖 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 1935 - 1944, 当前把 metadata 更新的同步签名仅用 primary storage UUID(在 thdf.chainSubmit 的 ChainTask.getSyncSignature 返回 "update-metadata-on-ps-" + self.getUuid())且 getSyncLevel() 返回 10,导致同一 VM 的并发 metadata 更新可能并行执行并互相覆盖。请把同步 key 收敛到 VM/根云盘粒度,例如在 getSyncSignature() 中包含 msg.getRootVolumeUuid() 或 metadataPath(例如 "update-metadata-on-ps-" + self.getUuid() + "-root-" + msg.getRootVolumeUuid()),并把 getSyncLevel() 改为 1,以确保同一卷的 metadata 更新按序执行;调整对应代码位置为 thdf.chainSubmit(...) 内的 ChainTask 匿名类中的 getSyncSignature() 和 getSyncLevel() 实现。compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
89-123:⚠️ Potential issue | 🔴 Critical不要把可能为
null的hostUuid固化到 cleanup/GC 里。expunge 场景下
hostUuid/lastHostUuid都可能拿不到。这里仍然把它写进cmsg和 GC 字段,第一次 cleanup 失败后,后续 GC 重试也拿不到可路由的宿主机,会一直重复失败。更稳妥的是:拿不到 host 时不要固化空值,改成在提交 GC 前补一个兜底 host,或把 host 选择延后到 GC/handler 运行时再做。🤖 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 - 123, In VmExpungeMetadataFlow, avoid persisting a null hostUuid into the outgoing cmsg and into the GC record: if spec.getVmInventory().getHostUuid()/getLastHostUuid() yields null, do not call cmsg.setHostUuid(hostUuid) with a null value and do not set CleanupVmInstanceMetadataOnPrimaryStorageGC.hostUuid to null; instead either (a) resolve a safe fallback host for the primary storage (e.g. a helper that picks any reachable host for psUuid) and populate cmsg and gc.hostUuid with that fallback before sending/submitting, or (b) remove host selection here and leave gc.hostUuid unset and move the host resolution logic into the GC handler so the retry can pick a valid routable host at runtime. Ensure changes touch VmExpungeMetadataFlow.run/send block (cmsg usage) and submitGC(...) assignment of gc.hostUuid and keep other fields unchanged.
🧹 Nitpick comments (6)
header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java (2)
9-24: 可选:使用 Stream 简化批量解析逻辑。当前 for-loop 实现正确,但可以使用 Java Stream 使代码更简洁。
♻️ Stream 风格的实现
default List<String> batchResolveVmUuids(List<String> fieldValues) { - List<String> result = new ArrayList<>(); if (fieldValues == null || fieldValues.isEmpty()) { - return result; + return new ArrayList<>(); } - for (String v : fieldValues) { - if (v == null) { - continue; - } - String vmUuid = resolveVmUuid(v); - if (vmUuid != null) { - result.add(vmUuid); - } - } - return result; + return fieldValues.stream() + .filter(v -> v != null) + .map(this::resolveVmUuid) + .filter(uuid -> uuid != null) + .collect(java.util.stream.Collectors.toList()); }🤖 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/VmUuidFromApiResolver.java` around lines 9 - 24, The batchResolveVmUuids method currently uses an explicit for-loop to filter null inputs, call resolveVmUuid for each, and collect non-null results; refactor it to a Stream-based implementation: use Optional.ofNullable(fieldValues).orElseGet(Collections::emptyList).stream() (or Stream.ofNullable depending on style) to stream the values, filter out null entries, map each value via resolveVmUuid, filter out null results, and collect to a List; keep the method signature and behavior unchanged (return empty list when input is null/empty) and ensure references to batchResolveVmUuids and resolveVmUuid remain correct.
6-7: 建议为接口方法添加 Javadoc 注释。根据编码规范,接口方法必须配有有效的 Javadoc 注释,说明方法用途、参数含义及返回值。
📝 建议添加 Javadoc
public interface VmUuidFromApiResolver { + /** + * Resolves a VM UUID from the given API field value. + * + * `@param` fieldValue the field value extracted from API message + * `@return` the resolved VM UUID, or null if resolution fails + */ String resolveVmUuid(String fieldValue);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/VmUuidFromApiResolver.java` around lines 6 - 7, Add proper Javadoc to the VmUuidFromApiResolver interface and its method resolveVmUuid(String fieldValue): document the interface purpose, explain the parameter fieldValue and what constitutes a valid input, and describe the return value (what VM UUID it returns or null/exception semantics). Ensure the method's Javadoc appears directly above the resolveVmUuid declaration and that no unnecessary modifiers are added to the interface method declaration (leave it with default interface visibility).testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy (1)
604-618: 建议:考虑添加 VFS hooks 以支持更完整的测试场景当前仅注册了返回空响应的基础模拟器,对于初始集成测试是足够的。但如果需要测试以下场景,建议后续添加 VFS hooks:
SCAN_VM_METADATA_PATH: 返回实际扫描到的元数据条目WRITE_VM_METADATA_PATH: 在 VFS 中持久化元数据GET_VM_INSTANCE_METADATA_PATH: 从 VFS 读取已写入的元数据可参考
VFS.vfsHook(LocalStorageKvmBackend.CREATE_VOLUME_FROM_CACHE_PATH, ...)等现有实现模式。🤖 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 simulators for LocalStorageKvmBackend currently return empty responses only; add VFS hooks using VFS.vfsHook similar to the existing CREATE_VOLUME_FROM_CACHE_PATH pattern to persist and serve VM metadata so tests can exercise full flows: implement hooks for LocalStorageKvmBackend.WRITE_VM_METADATA_PATH to store metadata into an in-memory VFS store and return LocalStorageKvmBackend.WriteVmMetadataRsp, for GET_VM_INSTANCE_METADATA_PATH to read and return stored metadata via LocalStorageKvmBackend.GetVmInstanceMetadataRsp, for SCAN_VM_METADATA_PATH to enumerate and return found entries via LocalStorageKvmBackend.ScanVmMetadataRsp, and ensure CLEANUP_VM_METADATA_PATH removes entries and returns LocalStorageKvmBackend.CleanupVmMetadataRsp; keep the response object types from the diff and use the same keying scheme the other VFS hooks use so tests can read/write/scan/cleanup metadata.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)
29-38: 建议重用resolveBaseDir方法以保持一致性。
calculatePathReplacements方法中的 mountPath/url 查询和路径规范化逻辑与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 + "/";Also applies to: 80-90
🤖 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 29 - 38, The calculatePathReplacements method duplicates the mountPath/url query and trailing-slash normalization logic found in resolveBaseDir; remove that duplication by calling resolveBaseDir(primaryStorageUuid) from calculatePathReplacements instead of re-querying PrimaryStorageVO (PrimaryStorageVO_.mountPath / PrimaryStorageVO_.url) and redoing the endsWith("/") trim, and propagate or handle the CloudRuntimeException from resolveBaseDir so behavior remains identical.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (1)
98-120: 存在与NfsVmMetadataExtension的代码重复。
validateMetadataPath和normalizeBaseDir的实现与NfsVmMetadataExtension几乎完全相同。考虑到后续可能新增更多存储类型的扩展,建议将通用逻辑提取到基类或工具类中。🤖 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 98 - 120, The LocalStorageVmMetadataExtension duplicates logic from NfsVmMetadataExtension for validateMetadataPath and normalizeBaseDir; extract the shared logic into a common base class or utility (e.g., VmMetadataExtensionBase or VmMetadataUtils) and have both LocalStorageVmMetadataExtension and NfsVmMetadataExtension delegate to it. Move the validation routine (using buildMetadataDir, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX, the prefix/suffix checks, and the 32-char hex regex) and the normalizeBaseDir implementation into the shared class/utility, update both classes to call the common methods, and ensure buildMetadataDir remains usable (or also refactor it into the base) so behavior is unchanged.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3907-3912: 可简化metadataEntries的空值分支,减少重复分支处理。Line 3907-3912 可以统一为“先归一化为非空列表,再设置 hostUuid 并回填 reply”,逻辑更直观。
♻️ 建议写法
public void success(ScanVmMetadataRsp rsp) { ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); - if (rsp.metadataEntries != null) { - rsp.metadataEntries.forEach(entry -> entry.setHostUuid(hostUuid)); - reply.setVmInstanceMetadata(rsp.metadataEntries); - } else { - reply.setVmInstanceMetadata(Collections.emptyList()); - } + List<VmMetadataScanEntry> entries = rsp.metadataEntries == null + ? Collections.emptyList() + : rsp.metadataEntries; + entries.forEach(entry -> entry.setHostUuid(hostUuid)); + reply.setVmInstanceMetadata(entries); completion.success(reply); }🤖 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 3907 - 3912, The null-check for rsp.metadataEntries can be simplified by normalizing it to a non-null list and then performing the hostUuid assignment and reply population once; replace the conditional with code that assigns List metadata = rsp.metadataEntries == null ? Collections.emptyList() : rsp.metadataEntries, then iterate metadata to call entry.setHostUuid(hostUuid) and finally call reply.setVmInstanceMetadata(metadata) (referencing rsp.metadataEntries, entry.setHostUuid, and reply.setVmInstanceMetadata).
🤖 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/tag/APIDeleteTagMsg.java`:
- Line 16: APIDeleteTagMsg 使用了仅适用于系统标签的 SystemTagUuidToVmUuidResolver
导致非系统标签删除时元数据影响解析错误;请在 APIDeleteTagMsg 上替换或移除该注解:要么实现并引用一个通用的
TagUuidToVmUuidResolver(支持系统标签、用户标签和标签模式)并将注解的 resolver 字段改为该类名,以保证 UUID
正确解析和元数据影响跟踪;要么直接移除 APIDeleteTagMsg 上的 `@MetadataImpact` 注解(若仅系统标签需要 VM
元数据跟踪),两种方案任选其一并在相应的单元/集成测试中验证删除系统与非系统标签时的元数据影响行为。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3924-3929: In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg
msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) validate that hostUuid is non-null before calling
httpCall(CLEANUP_VM_METADATA_PATH,...); if hostUuid is null, either fail-fast by
completing the completion with an error reply or attempt a fallback resolution
(e.g., derive hostUuid from fields on
CleanupVmInstanceMetadataOnPrimaryStorageMsg such as a host reference or
vm/volume identifiers) and only proceed to construct CleanupVmMetadataCmd and
invoke httpCall when a valid hostUuid is obtained; ensure the failure path sets
a clear error on CleanupVmInstanceMetadataOnPrimaryStorageReply so callers can
handle the routing failure.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java`:
- Around line 63-66: The new metadata command lists
(LocalStorageSimulatorConfig.writeVmMetadataCmds, getVmInstanceMetadataCmds,
scanVmMetadataCmds, cleanupVmMetadataCmds) are being modified without
synchronization; wrap all modifications to these lists inside
synchronized(config) blocks in the handlers writeVmMetadata(),
getVmInstanceMetadata(), scanVmMetadata(), and cleanupVmMetadata() in
LocalStorageSimulator so additions use e.g. synchronized(config) {
config.writeVmMetadataCmds.add(cmd); } (do the same pattern for the other three
lists) to prevent concurrent modification races.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2089-2095: The success branches for handling WriteVmMetadataRsp
and CleanupVmMetadataRsp are missing the primary storage capacity refresh; after
confirming rsp.isSuccess() (in the blocks handling WriteVmMetadataRsp and
CleanupVmMetadataRsp) call updatePrimaryStorageCapacity(...) the same way
asyncHttpCall(...) does before calling completion.success(...), so capacity is
recalculated after metadata file create/delete; reference the response classes
WriteVmMetadataRsp and CleanupVmMetadataRsp, the
updatePrimaryStorageCapacity(...) helper, and ensure you still invoke
completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()) after
the capacity update.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1889-1892: 当前代码在构建 VM 元数据扫描结果时仅排除了
VmMetadataCategory.TEMPLATE_CACHE,导致 CATEGORY 为 TEMPLATE 的条目也被返回给 VM(通过
evt.setVmInstanceMetadata),使结果集中包含非 VM 元数据;请修改过滤逻辑(在处理 metadata 的流中,对
VmMetadataScanEntry 使用白名单过滤),只保留 VmMetadataCategory.REGULAR 的条目(识别符例如
VmMetadataScanEntry#getVmCategory、VmMetadataCategory、evt.setVmInstanceMetadata、metadata),确保只有
REGULAR 类型被收集并传入 evt.setVmInstanceMetadata。
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-123: In VmExpungeMetadataFlow, avoid persisting a null hostUuid
into the outgoing cmsg and into the GC record: if
spec.getVmInventory().getHostUuid()/getLastHostUuid() yields null, do not call
cmsg.setHostUuid(hostUuid) with a null value and do not set
CleanupVmInstanceMetadataOnPrimaryStorageGC.hostUuid to null; instead either (a)
resolve a safe fallback host for the primary storage (e.g. a helper that picks
any reachable host for psUuid) and populate cmsg and gc.hostUuid with that
fallback before sending/submitting, or (b) remove host selection here and leave
gc.hostUuid unset and move the host resolution logic into the GC handler so the
retry can pick a valid routable host at runtime. Ensure changes touch
VmExpungeMetadataFlow.run/send block (cmsg usage) and submitGC(...) assignment
of gc.hostUuid and keep other fields unchanged.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 2039-2060: The cleanup handler currently calls backend.handle(...)
directly (in handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) and bypasses
the ChainTask serialization used by update, risking race with register/update;
change this to enqueue the cleanup as a ChainTask using the same sync signature
logic as the update path (serialize by rootVolumeUuid or metadataPath) so
cleanup runs on the same serialized channel; locate the handler that builds
ChainTask for update (the update/register path that composes the sync signature)
and reuse that signature and queuing pattern instead of calling backend.handle
directly, then inside the ChainTask invoke the backend cleanup and reply with
the existing CleanupVmInstanceMetadataOnPrimaryStorageReply on success/failure.
- Around line 1935-1944: 当前把 metadata 更新的同步签名仅用 primary storage UUID(在
thdf.chainSubmit 的 ChainTask.getSyncSignature 返回 "update-metadata-on-ps-" +
self.getUuid())且 getSyncLevel() 返回 10,导致同一 VM 的并发 metadata 更新可能并行执行并互相覆盖。请把同步
key 收敛到 VM/根云盘粒度,例如在 getSyncSignature() 中包含 msg.getRootVolumeUuid() 或
metadataPath(例如 "update-metadata-on-ps-" + self.getUuid() + "-root-" +
msg.getRootVolumeUuid()),并把 getSyncLevel() 改为 1,以确保同一卷的 metadata
更新按序执行;调整对应代码位置为 thdf.chainSubmit(...) 内的 ChainTask 匿名类中的 getSyncSignature() 和
getSyncLevel() 实现。
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2173-2177: The code currently uses the incoming hostUuid directly
when building the KVMHostAsyncHttpCallMsg (KVMHostAsyncHttpCallMsg, setHostUuid,
makeTargetServiceIdByResourceUuid), which breaks GC/expunge flows if hostUuid is
null or the host is unavailable; change the logic to validate the incoming
hostUuid and if it is null or not reachable, call
nfsFactory.getConnectedHostForOperation(...) to select a usable connected host,
set that host's UUID on the message via setHostUuid(...), and then call
bus.makeTargetServiceIdByResourceUuid(...) with the selected host UUID so the
cleanup can be routed to any available NFS-mounted host.
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 9-24: The batchResolveVmUuids method currently uses an explicit
for-loop to filter null inputs, call resolveVmUuid for each, and collect
non-null results; refactor it to a Stream-based implementation: use
Optional.ofNullable(fieldValues).orElseGet(Collections::emptyList).stream() (or
Stream.ofNullable depending on style) to stream the values, filter out null
entries, map each value via resolveVmUuid, filter out null results, and collect
to a List; keep the method signature and behavior unchanged (return empty list
when input is null/empty) and ensure references to batchResolveVmUuids and
resolveVmUuid remain correct.
- Around line 6-7: Add proper Javadoc to the VmUuidFromApiResolver interface and
its method resolveVmUuid(String fieldValue): document the interface purpose,
explain the parameter fieldValue and what constitutes a valid input, and
describe the return value (what VM UUID it returns or null/exception semantics).
Ensure the method's Javadoc appears directly above the resolveVmUuid declaration
and that no unnecessary modifiers are added to the interface method declaration
(leave it with default interface visibility).
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3907-3912: The null-check for rsp.metadataEntries can be
simplified by normalizing it to a non-null list and then performing the hostUuid
assignment and reply population once; replace the conditional with code that
assigns List metadata = rsp.metadataEntries == null ? Collections.emptyList() :
rsp.metadataEntries, then iterate metadata to call entry.setHostUuid(hostUuid)
and finally call reply.setVmInstanceMetadata(metadata) (referencing
rsp.metadataEntries, entry.setHostUuid, and reply.setVmInstanceMetadata).
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 98-120: The LocalStorageVmMetadataExtension duplicates logic from
NfsVmMetadataExtension for validateMetadataPath and normalizeBaseDir; extract
the shared logic into a common base class or utility (e.g.,
VmMetadataExtensionBase or VmMetadataUtils) and have both
LocalStorageVmMetadataExtension and NfsVmMetadataExtension delegate to it. Move
the validation routine (using buildMetadataDir,
VmInstanceMetadataConstants.FILE_METADATA_SUFFIX, the prefix/suffix checks, and
the 32-char hex regex) and the normalizeBaseDir implementation into the shared
class/utility, update both classes to call the common methods, and ensure
buildMetadataDir remains usable (or also refactor it into the base) so behavior
is unchanged.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 29-38: The calculatePathReplacements method duplicates the
mountPath/url query and trailing-slash normalization logic found in
resolveBaseDir; remove that duplication by calling
resolveBaseDir(primaryStorageUuid) from calculatePathReplacements instead of
re-querying PrimaryStorageVO (PrimaryStorageVO_.mountPath /
PrimaryStorageVO_.url) and redoing the endsWith("/") trim, and propagate or
handle the CloudRuntimeException from resolveBaseDir so behavior remains
identical.
In `@testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy`:
- Around line 604-618: The simulators for LocalStorageKvmBackend currently
return empty responses only; add VFS hooks using VFS.vfsHook similar to the
existing CREATE_VOLUME_FROM_CACHE_PATH pattern to persist and serve VM metadata
so tests can exercise full flows: implement hooks for
LocalStorageKvmBackend.WRITE_VM_METADATA_PATH to store metadata into an
in-memory VFS store and return LocalStorageKvmBackend.WriteVmMetadataRsp, for
GET_VM_INSTANCE_METADATA_PATH to read and return stored metadata via
LocalStorageKvmBackend.GetVmInstanceMetadataRsp, for SCAN_VM_METADATA_PATH to
enumerate and return found entries via LocalStorageKvmBackend.ScanVmMetadataRsp,
and ensure CLEANUP_VM_METADATA_PATH removes entries and returns
LocalStorageKvmBackend.CleanupVmMetadataRsp; keep the response object types from
the diff and use the same keying scheme the other VFS hooks use so tests can
read/write/scan/cleanup metadata.
🪄 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: c29bac9a-3bc6-4dbb-b607-af8077f1658a
⛔ 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 (144)
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/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/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/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/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 (82)
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.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/volume/APIFlattenVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- sdk/src/main/java/SourceClassMap.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
🚧 Files skipped from review as they are similar to previous changes (33)
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/VmInstanceState.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
| method = HttpMethod.DELETE, | ||
| responseClass = APIDeleteTagEvent.class | ||
| ) | ||
| @MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SystemTagUuidToVmUuidResolver", field = "uuid") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查找 SystemTagUuidToVmUuidResolver 的实现,确认其是否能处理非系统标签
ast-grep --pattern $'class SystemTagUuidToVmUuidResolver {
$$$
}'
# 查找该 resolver 的 resolveVmUuids 方法实现
rg -n -A 20 'class SystemTagUuidToVmUuidResolver'Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Search for MetadataImpact annotation usage patterns
rg -n "MetadataImpact.*resolver" --type java -A 2Repository: MatheMatrix/zstack
Length of output: 29381
🏁 Script executed:
# Find APIDeleteTagMsg definition
fd -e java -x grep -l "class APIDeleteTagMsg" {} \;Repository: MatheMatrix/zstack
Length of output: 127
🏁 Script executed:
# Search for resolver implementations
rg -n "Resolver.*class|class.*Resolver" --type java | head -30Repository: MatheMatrix/zstack
Length of output: 462
🏁 Script executed:
# Check for SystemTagUuidToVmUuidResolver references
rg -n "SystemTagUuidToVmUuidResolver" --type javaRepository: MatheMatrix/zstack
Length of output: 820
🏁 Script executed:
# Find system tag related APIs
rg -n "APICreateSystemTagMsg|APIDeleteSystemTagMsg" --type java | head -20Repository: MatheMatrix/zstack
Length of output: 906
🏁 Script executed:
# Read APIDeleteTagMsg.java file
cat -n header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.javaRepository: MatheMatrix/zstack
Length of output: 1214
🏁 Script executed:
# Check if there's a separate APIDeleteSystemTagMsg
rg -l "class APIDeleteSystemTagMsg" --type javaRepository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Search for tag deletion patterns to understand the design
rg -n "deleteTag|delete.*tag" --type java -i | grep -i "api\|msg" | head -20Repository: MatheMatrix/zstack
Length of output: 2504
🏁 Script executed:
# Check the resolver implementation
rg -n "class SystemTagUuidToVmUuidResolver" --type java -A 30Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Search for resolver implementations with different patterns
rg -n "SystemTagUuidToVmUuidResolver" --type javaRepository: MatheMatrix/zstack
Length of output: 820
🏁 Script executed:
# Look for MetadataImpact resolver implementations
rg -n "MetadataImpactResolver\|extends.*Resolver" --type java | grep -i "metadata\|tag" | head -20Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Check tag management for understanding how system tags vs user tags are handled
cat -n tag/src/main/java/org/zstack/tag/TagManagerImpl.java | head -100Repository: MatheMatrix/zstack
Length of output: 5161
🏁 Script executed:
# Look for metadata impact related code
fd -e java -x grep -l "MetadataImpact" {} \; | head -10Repository: MatheMatrix/zstack
Length of output: 888
🏁 Script executed:
# Search for resolver interfaces/base classes
rg -n "interface.*Resolver|abstract.*Resolver" --type java | head -20Repository: MatheMatrix/zstack
Length of output: 182
🏁 Script executed:
# Find implementations of VmUuidFromApiResolver
rg -n "implements VmUuidFromApiResolver" --type javaRepository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Search for any class that might be the SystemTagUuidToVmUuidResolver
rg -n "SystemTagUuidToVmUuidResolver" --type java -A 1 -B 1Repository: MatheMatrix/zstack
Length of output: 1649
🏁 Script executed:
# Look at the VmUuidFromApiResolver interface definition
cat -n header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.javaRepository: MatheMatrix/zstack
Length of output: 913
🏁 Script executed:
# Search for resolver registration or bean definitions
rg -n "@Bean.*Resolver|resolver.*bean" --type java -i | head -20Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Check TagApiInterceptor to understand how deletion works for both tag types
cat -n tag/src/main/java/org/zstack/tag/TagApiInterceptor.java | sed -n '98,160p'Repository: MatheMatrix/zstack
Length of output: 3646
🏁 Script executed:
# Search for resolver implementation in tag module
find tag -name "*.java" -type f | xargs grep -l "SystemTagUuidToVmUuidResolver"Repository: MatheMatrix/zstack
Length of output: 46
🏁 Script executed:
# Search more broadly for any resolver implementations
rg -n "class.*VmUuidFromApiResolver|implements VmUuidFromApiResolver" --type java -A 10Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Look for metadata impact framework to understand how resolvers are used
rg -n "class.*MetadataImpact|interface.*MetadataImpact" --type javaRepository: MatheMatrix/zstack
Length of output: 169
🏁 Script executed:
# Check if there's explicit handling in tag deletion for system vs user tags
cat -n tag/src/main/java/org/zstack/tag/TagManagerImpl.java | sed -n '726,760p'Repository: MatheMatrix/zstack
Length of output: 1814
🏁 Script executed:
# Read the MetadataImpact annotation definition
cat -n header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.javaRepository: MatheMatrix/zstack
Length of output: 1434
🏁 Script executed:
# Search for actual resolver implementations or bean definitions
find . -name "*.java" -type f | xargs grep -l "VmUuidFromApiResolver" 2>/dev/null | head -10Repository: MatheMatrix/zstack
Length of output: 214
🏁 Script executed:
# Check for any resolver bean configurations
rg -n "@Component|@Bean|@Service.*Resolver" --type java | grep -i "resolver\|tag" | head -20Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Look for how the resolver string is resolved/used in metadata framework
rg -n "resolver.*=|getResolver" --type java | head -30Repository: MatheMatrix/zstack
Length of output: 6014
🏁 Script executed:
# Search for metadata impact interceptor/framework implementation
rg -n "class.*MetadataImpactInterceptor|MetadataImpact.*interceptor" --type java -A 10 -iRepository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Look for how the resolver is invoked and what happens on failure/null
rg -n "resolveVmUuid|batchResolveVmUuids" --type java | head -20Repository: MatheMatrix/zstack
Length of output: 605
🏁 Script executed:
# Check for null/empty handling in resolver usage
rg -n "resolveVmUuids.*null|== null.*resolveVmUuids" --type javaRepository: MatheMatrix/zstack
Length of output: 44
使用通用标签删除 API 应避免使用系统标签专用 resolver。
APIDeleteTagMsg 是通用标签删除 API,可删除系统标签、用户标签和标签模式,但注解使用的 SystemTagUuidToVmUuidResolver 仅适用于系统标签。当删除非系统标签时,该 resolver 无法正确解析 UUID,导致元数据影响跟踪失效。
建议:
- 为通用标签删除创建专用 resolver(如
TagUuidToVmUuidResolver),支持所有标签类型;或 - 移除
@MetadataImpact注解,因为只有系统标签需要 VM 元数据跟踪。
参考现有设计:其他通用 DELETE API(如 APIDeleteResourceConfigMsg)使用专用 resolver(ConfigUuidToVmUuidResolver)处理资源特定的元数据映射。
🤖 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/tag/APIDeleteTagMsg.java` at line 16,
APIDeleteTagMsg 使用了仅适用于系统标签的 SystemTagUuidToVmUuidResolver
导致非系统标签删除时元数据影响解析错误;请在 APIDeleteTagMsg 上替换或移除该注解:要么实现并引用一个通用的
TagUuidToVmUuidResolver(支持系统标签、用户标签和标签模式)并将注解的 resolver 字段改为该类名,以保证 UUID
正确解析和元数据影响跟踪;要么直接移除 APIDeleteTagMsg 上的 `@MetadataImpact` 注解(若仅系统标签需要 VM
元数据跟踪),两种方案任选其一并在相应的单元/集成测试中验证删除系统与非系统标签时的元数据影响行为。
| 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 |
There was a problem hiding this comment.
清理元数据前缺少 hostUuid 兜底/校验,存在路由失败风险。
Line 3928 直接使用 hostUuid 发起 httpCall。当 hostUuid 为空时,会在消息路由阶段失败,导致元数据清理无法执行。建议至少先 fail-fast,并优先补充 host 解析兜底逻辑后再下发。
🔧 最小修复(先避免隐式路由失败)
`@Override`
void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) {
+ if (StringUtils.isBlank(hostUuid)) {
+ completion.fail(operr("cleanup vm metadata failed: hostUuid is null, metadataPath=%s", msg.getMetadataPath()));
+ return;
+ }
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd();
cmd.metadataPath = msg.getMetadataPath();
httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) {🤖 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 - 3929, In handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg
msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) validate that hostUuid is non-null before calling
httpCall(CLEANUP_VM_METADATA_PATH,...); if hostUuid is null, either fail-fast by
completing the completion with an error reply or attempt a fallback resolution
(e.g., derive hostUuid from fields on
CleanupVmInstanceMetadataOnPrimaryStorageMsg such as a host reference or
vm/volume identifiers) and only proceed to construct CleanupVmMetadataCmd and
invoke httpCall when a valid hostUuid is obtained; ensure the failure path sets
a clear error on CleanupVmInstanceMetadataOnPrimaryStorageReply so callers can
handle the routing failure.
| public List<WriteVmMetadataCmd> writeVmMetadataCmds = new ArrayList<>(); | ||
| public List<GetVmInstanceMetadataCmd> getVmInstanceMetadataCmds = new ArrayList<>(); | ||
| public List<ScanVmMetadataCmd> scanVmMetadataCmds = new ArrayList<>(); | ||
| public List<CleanupVmMetadataCmd> cleanupVmMetadataCmds = new ArrayList<>(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证 LocalStorageSimulator.java 中新增的元数据命令处理器是否使用了 synchronized(config) 同步块
# 搜索 writeVmMetadata、getVmInstanceMetadata、scanVmMetadata、cleanupVmMetadata 相关的处理器
rg -n -A 10 'writeVmMetadataCmds|getVmInstanceMetadataCmds|scanVmMetadataCmds|cleanupVmMetadataCmds' --type javaRepository: MatheMatrix/zstack
Length of output: 6571
🏁 Script executed:
# Check the synchronization pattern in LocalStorageSimulator.java around lines 235-260
sed -n '230,270p' plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.javaRepository: MatheMatrix/zstack
Length of output: 1693
新增的元数据命令处理器缺少线程同步保护
虽然新增的四个字段遵循了现有的命令列表模式,但是在 LocalStorageSimulator.java 中对这些列表的访问缺少必要的同步保护。经检查发现:
delete()和deleteDir()处理器(第 243-248、250-257 行)正确地使用synchronized(config)块包装 ArrayList 修改- 新增的
writeVmMetadata()、getVmInstanceMetadata()、scanVmMetadata()、cleanupVmMetadata()处理器(第 341、350、360、370 行)直接对列表调用.add()方法,未使用同步保护
这会导致多个并发 HTTP 请求在修改共享 ArrayList 时产生竞态条件。需要为所有新增的命令列表访问添加 synchronized(config) 块:
synchronized (config) {
config.writeVmMetadataCmds.add(cmd);
}🤖 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/LocalStorageSimulatorConfig.java`
around lines 63 - 66, The new metadata command lists
(LocalStorageSimulatorConfig.writeVmMetadataCmds, getVmInstanceMetadataCmds,
scanVmMetadataCmds, cleanupVmMetadataCmds) are being modified without
synchronization; wrap all modifications to these lists inside
synchronized(config) blocks in the handlers writeVmMetadata(),
getVmInstanceMetadata(), scanVmMetadata(), and cleanupVmMetadata() in
LocalStorageSimulator so additions use e.g. synchronized(config) {
config.writeVmMetadataCmds.add(cmd); } (do the same pattern for the other three
lists) to prevent concurrent modification races.
| WriteVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(WriteVmMetadataRsp.class); | ||
| if (!rsp.isSuccess()) { | ||
| completion.fail(operr("failed to write vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); | ||
| return; | ||
| } | ||
|
|
||
| completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); |
There was a problem hiding this comment.
写/删元数据成功后把容量刷新漏掉了。
WriteVmMetadataRsp 和 CleanupVmMetadataRsp 都继承了 NfsPrimaryStorageAgentResponse,但这里成功后没有像 asyncHttpCall(...) 那样调用 updatePrimaryStorageCapacity(...)。元数据文件的创建/删除会让主存容量在下一次 ping 前保持陈旧。
💡 建议修改
WriteVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(WriteVmMetadataRsp.class);
if (!rsp.isSuccess()) {
completion.fail(operr("failed to write vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
return;
}
+ updatePrimaryStorageCapacity(msg.getPrimaryStorageUuid(), rsp);
completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply());
@@
CleanupVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(CleanupVmMetadataRsp.class);
if (!rsp.isSuccess()) {
completion.fail(operr("failed to cleanup vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError()));
return;
}
+ updatePrimaryStorageCapacity(msg.getPrimaryStorageUuid(), rsp);
CleanupVmInstanceMetadataOnPrimaryStorageReply r = new CleanupVmInstanceMetadataOnPrimaryStorageReply();
completion.success(r);Also applies to: 2186-2193
🤖 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 2089 - 2095, The success branches for handling WriteVmMetadataRsp
and CleanupVmMetadataRsp are missing the primary storage capacity refresh; after
confirming rsp.isSuccess() (in the blocks handling WriteVmMetadataRsp and
CleanupVmMetadataRsp) call updatePrimaryStorageCapacity(...) the same way
asyncHttpCall(...) does before calling completion.success(...), so capacity is
recalculated after metadata file create/delete; reference the response classes
WriteVmMetadataRsp and CleanupVmMetadataRsp, the
updatePrimaryStorageCapacity(...) helper, and ensure you still invoke
completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()) after
the capacity update.
| List<VmMetadataScanEntry> filtered = metadata.stream() | ||
| .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory())) | ||
| .collect(Collectors.toList()); | ||
| evt.setVmInstanceMetadata(filtered); |
There was a problem hiding this comment.
这里会把 TEMPLATE 条目也返回给 VM 扫描结果。
当前只排除了 TEMPLATE_CACHE,但 VmMetadataCategory 里还有 TEMPLATE。这些条目会直接进入 evt.setVmInstanceMetadata(...),结果集就不再是纯 VM metadata。这里更稳妥的是按 REGULAR 做白名单过滤。
💡 建议修改
- List<VmMetadataScanEntry> filtered = metadata.stream()
- .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory()))
- .collect(Collectors.toList());
+ List<VmMetadataScanEntry> filtered = metadata.stream()
+ .filter(e -> VmMetadataCategory.REGULAR.toString().equals(e.getVmCategory()))
+ .collect(Collectors.toList());📝 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.
| List<VmMetadataScanEntry> filtered = metadata.stream() | |
| .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory())) | |
| .collect(Collectors.toList()); | |
| evt.setVmInstanceMetadata(filtered); | |
| List<VmMetadataScanEntry> filtered = metadata.stream() | |
| .filter(e -> VmMetadataCategory.REGULAR.toString().equals(e.getVmCategory())) | |
| .collect(Collectors.toList()); | |
| evt.setVmInstanceMetadata(filtered); |
🤖 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 1889 - 1892, 当前代码在构建 VM 元数据扫描结果时仅排除了
VmMetadataCategory.TEMPLATE_CACHE,导致 CATEGORY 为 TEMPLATE 的条目也被返回给 VM(通过
evt.setVmInstanceMetadata),使结果集中包含非 VM 元数据;请修改过滤逻辑(在处理 metadata 的流中,对
VmMetadataScanEntry 使用白名单过滤),只保留 VmMetadataCategory.REGULAR 的条目(识别符例如
VmMetadataScanEntry#getVmCategory、VmMetadataCategory、evt.setVmInstanceMetadata、metadata),确保只有
REGULAR 类型被收集并传入 evt.setVmInstanceMetadata。
|
Comment from yaohua.wu: Review: MR !9469 — ZSV-11559 (zstackio/zstack)MR:
🟡 Warning1. [VmMetadataDirtyVO.java] VO 中 `lastOpDate` timestamp on update CURRENT_TIMESTAMP,缺少 建议:添加 2. [VmMetadataDirtyVO.java] SQL 定义 3. [VmExpungeMetadataFlow.java:~57] 获取 String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type)
.eq(PrimaryStorageVO_.uuid, psUuid).findValue();在 expunge flow 的热路径上额外查 DB 获取 PS type。如果 4. [VmExpungeMetadataFlow.java:~116-126] GC interval 使用 long gcIntervalSec = TimeUnit.HOURS.toSeconds(
VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class));
建议:加 try-catch 或使用默认值兜底。 5. [V5.0.0__schema.sql]
6. [VmInstanceApiInterceptor.java] String psType = Q.New(PrimaryStorageVO.class)...findValue();
VmMetadataPathBuildExtensionPoint ext = (psType != null)
? pluginRgty.getExtensionFromMap(psType, ...) : null;
if (ext == null) {
throw new ApiMessageInterceptionException(argerr(
"primary storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType));
}当 🟢 Suggestion1. [MetadataImpact.java] 注解的 注解声明 2. [VmMetadataErrors.java] 错误码使用数字 ID 拼接 code = String.format("VM_METADATA.%s", id);产生 3. [CleanupVmInstanceMetadataOnPrimaryStorageGC.java] GC 名称硬编码格式
4. [conf/globalConfig/vm.xml] 多个新增 GlobalConfig 缺少中文描述 新增的 Verdict: APPROVED (with minor suggestions)整体架构设计合理:
建议修复 Warning #1/#2(VO 缺少 @PrePersist)以确保审计字段完整。 🤖 Robot Reviewer |
5f516f9 to
146ae94
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (7)
storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1889-1892:⚠️ Potential issue | 🟠 Major扫描结果这里应该对白名单过滤
REGULAR。现在只排除了
TEMPLATE_CACHE,TEMPLATE仍然会进入evt.setVmInstanceMetadata(...),返回结果不再是纯 VM metadata。💡 建议修改
- List<VmMetadataScanEntry> filtered = metadata.stream() - .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory())) - .collect(Collectors.toList()); + List<VmMetadataScanEntry> filtered = metadata.stream() + .filter(e -> VmMetadataCategory.REGULAR.toString().equals(e.getVmCategory())) + .collect(Collectors.toList());🤖 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 1889 - 1892, The filtered metadata currently only excludes VmMetadataCategory.TEMPLATE_CACHE, but TEMPLATE entries still pass through; update the filtering in PrimaryStorageBase (the stream that produces 'filtered' before evt.setVmInstanceMetadata) to whitelist only VmMetadataCategory.REGULAR (or explicitly exclude both TEMPLATE and TEMPLATE_CACHE) so evt.setVmInstanceMetadata receives pure VM metadata; adjust the lambda that checks e.getVmCategory() accordingly.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3924-3928:⚠️ Potential issue | 🔴 Critical这里需要在下发前兜底或拒绝空
hostUuid。
httpCall(...)会按hostUuid路由到宿主机服务;在 expunge/GC 场景传入空值时,这里会直接在路由阶段失败,元数据清理根本不会执行。🔧 最小修复
`@Override` void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { + if (StringUtils.isBlank(hostUuid)) { + completion.fail(operr("cleanup vm metadata failed: hostUuid is null, metadataPath=%s", msg.getMetadataPath())); + return; + } CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); cmd.metadataPath = msg.getMetadataPath();🤖 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, The handle method should guard against a null/empty hostUuid before calling httpCall: in LocalStorageKvmBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>) validate msg.getMetadataPath() and hostUuid (the latter must not be null/empty) and if hostUuid is missing immediately complete with an error via the provided completion (e.g., completion.fail(...) or construct a failed CleanupVmInstanceMetadataOnPrimaryStorageReply), otherwise proceed to create CleanupVmMetadataCmd and call httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, ...). Ensure the error message clearly states hostUuid is empty so routing is not attempted.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2089-2095:⚠️ Potential issue | 🟡 Minor写/删 metadata 成功后仍然漏掉容量刷新。
这两个分支绕过了
asyncHttpCall(...),成功后没有调用updatePrimaryStorageCapacity(...)。metadata 文件在 NFS 上创建/删除后,主存容量会一直保留旧值到下一次 ping。补上容量刷新的最小改动
WriteVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(WriteVmMetadataRsp.class); if (!rsp.isSuccess()) { completion.fail(operr("failed to write vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); return; } + updatePrimaryStorageCapacity(msg.getPrimaryStorageUuid(), rsp); completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); @@ CleanupVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(CleanupVmMetadataRsp.class); if (!rsp.isSuccess()) { completion.fail(operr("failed to cleanup vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); return; } + updatePrimaryStorageCapacity(msg.getPrimaryStorageUuid(), rsp); CleanupVmInstanceMetadataOnPrimaryStorageReply r = new CleanupVmInstanceMetadataOnPrimaryStorageReply();Also applies to: 2186-2193
🤖 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 2089 - 2095, The write/delete metadata success branches return without refreshing primary storage capacity; after receiving a successful WriteVmMetadataRsp (from KVMHostAsyncHttpCallReply) you must call updatePrimaryStorageCapacity(...) to refresh NFS usage before calling completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); locate the success branches handling WriteVmMetadataRsp (and the analogous branch around lines 2186-2193) and insert a call to updatePrimaryStorageCapacity with the host/primary storage identifiers and a callback that then calls completion.success(...) (or completion.fail(...) on error) so capacity is updated immediately after metadata create/delete.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (2)
1935-1944:⚠️ Potential issue | 🔴 Critical同一份 metadata 仍然可能并发写入。
现在
getSyncSignature()只按 primary storage 分桶,但getSyncLevel()允许 10 并发;同一个metadataPath的两个更新仍然会同时落到后端,覆盖写文件时会出现丢更新或半写状态。这里至少要把同步粒度收敛到单个 metadata 文件,并把 sync level 收到 1。把同步键收敛到单个 metadata 文件
`@Override` public String getSyncSignature() { - return "update-metadata-on-ps-" + self.getUuid(); + String key = msg.getMetadataPath() != null ? msg.getMetadataPath() : msg.getRootVolumeUuid(); + return "vm-metadata-on-ps-" + self.getUuid() + "-" + key; } `@Override` public int getSyncLevel() { - return 10; + return 1; }🤖 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 1935 - 1944, 当前并发控制以 primary storage 为粒度(getSyncSignature() 返回 "update-metadata-on-ps-"+self.getUuid())但 getSyncLevel() 允许 10 个并发,导致同一 metadataPath 的并发写入会覆盖或半写;在提交 thdf.chainSubmit(new ChainTask(msg) { ... }) 时,将 getSyncSignature() 收敛到单个 metadata 文件(例如包含 metadataPath 或 metadata 文件名/UUID 而非仅 self.getUuid()),并把 getSyncLevel() 返回值改为 1,以确保对同一 metadataPath 的更新串行化(参考 ChainTask, thdf.chainSubmit, getSyncSignature(), getSyncLevel(), metadataPath)。
2038-2060:⚠️ Potential issue | 🔴 Criticalcleanup 仍然绕过了 metadata 串行化通道。
UpdateVmInstanceMetadataOnPrimaryStorageMsg已经走了ChainTask,但 cleanup 这里还是直接下发 backend。只要 expunge/GC 和 update/register 并发命中同一个metadataPath,cleanup 就可能删掉刚写好的文件,或者打断正在进行的写入。这里需要复用和 update 相同的 sync key,把 cleanup 也放进同一条串行化链路。🤖 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 2038 - 2060, handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg) currently calls backend.handle(...) directly, bypassing the serialization used by UpdateVmInstanceMetadataOnPrimaryStorageMsg and risking races; change it to compute the same sync key (the metadataPath/key used by UpdateVmInstanceMetadataOnPrimaryStorageMsg) and submit a ChainTask (using the same chain/queue mechanism) that runs the backend.handle(...) inside its run() so cleanup executes serialized with update/register operations; keep existing Reply/ReturnValueCompletion semantics (use the same reply object and error handling) and still obtain the hostUuid via factory.getConnectedHostForOperation/getBackendByHostUuid before invoking the chained task.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)
337-372:⚠️ Potential issue | 🟡 Minorsimulator 的 metadata 读写状态没有连起来。
这里
writeVmMetadata()/cleanupVmMetadata()只记录 command,getVmInstanceMetadata()/scanVmMetadata()又直接回空 rsp。这样在 simulator 下,写后读、删后扫都会得到“调用成功但结果为空”的假阳性,新增 metadata 路径的测试覆盖不到真实行为。🤖 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's metadata lifecycle is broken: writeVmMetadata() and cleanupVmMetadata() only record commands while getVmInstanceMetadata() and scanVmMetadata() always return empty responses; fix this in LocalStorageSimulator by maintaining an in-memory metadata store keyed by VM/volume IDs (e.g., add a Map in the simulator or extend config to hold metadata entries), update writeVmMetadata() to persist the metadata into that store when handling WriteVmMetadataCmd (in addition to appending to config.writeVmMetadataCmds), update getVmInstanceMetadata() to read from the store and populate GetVmInstanceMetadataRsp, update scanVmMetadata() to return matching entries from the store into ScanVmMetadataRsp, and update cleanupVmMetadata() to remove entries from the store when handling CleanupVmMetadataCmd (while still recording config.cleanupVmMetadataCmds); ensure the response objects mirror the stored data so tests see realistic read/scan/cleanup behavior.compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
89-93:⚠️ Potential issue | 🟠 MajorhostUuid 为 null 时 GC 重试将持续失败。
对于已 expunge 的 VM,
hostUuid和lastHostUuid可能都为 null。将 null 的hostUuid传递给CleanupVmInstanceMetadataOnPrimaryStorageMsg和 GC 会导致消息路由失败,GC 会重复失败而无法完成清理。建议在 GC 执行时动态查找可用主机,而非在提交时固定 hostUuid:
- 如果 hostUuid 为 null,可以通过
PrimaryStorageClusterRefVO+HostVO查找该主存储关联集群下的任意可用主机- 或者在 handler 端实现主机选择逻辑
🤖 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 currently sets a possibly-null hostUuid on CleanupVmInstanceMetadataOnPrimaryStorageMsg (in VmExpungeMetadataFlow by using spec.getVmInventory().getHostUuid() / getLastHostUuid()), which causes GC routing to fail when both are null; instead, remove assigning a null hostUuid here and implement host selection either by (A) querying PrimaryStorageClusterRefVO + HostVO to pick any available host in the primary storage's clusters and set that hostUuid before sending the message, or (B) leave hostUuid unset and add selection logic in the CleanupVmInstanceMetadataOnPrimaryStorageMsg handler to lookup PrimaryStorageClusterRefVO and HostVO and choose a live host at runtime; update VmExpungeMetadataFlow (the code that constructs cmsg) and/or the handler to perform the DB query and set a non-null hostUuid only when an actual host is found.
🧹 Nitpick comments (6)
sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java (2)
1-6: 冗余的同包导入
import org.zstack.sdk.*;是不必要的,因为当前类已经位于org.zstack.sdk包中,同包内的类无需导入即可直接访问。♻️ 建议移除冗余导入
package org.zstack.sdk; import java.util.HashMap; import java.util.Map; -import org.zstack.sdk.*; public class GetVmInstanceMetadataFromPrimaryStorageAction extends AbstractAction {🤖 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 1 - 6, Remove the redundant same-package wildcard import in GetVmInstanceMetadataFromPrimaryStorageAction: the class is already declared in package org.zstack.sdk so delete the line "import org.zstack.sdk.*;" and keep only the necessary imports (e.g., java.util.HashMap, java.util.Map) so classes in org.zstack.sdk are accessed directly without that import.
31-35: 原始 List 类型缺少泛型参数
systemTags和userTags字段使用原始List类型,建议添加泛型参数以提高类型安全性。♻️ 建议添加泛型参数
`@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, 在 GetVmInstanceMetadataFromPrimaryStorageAction 类中,systemTags 和 userTags 字段目前使用原始类型 java.util.List;请改为使用带泛型的类型(例如 java.util.List<String> 或其他合适的元素类型)以提升类型安全性,并更新相应的声明(字段名 systemTags、userTags)及必要的导入/序列化注解匹配。header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java (1)
5-5: 未使用的导入VmInstanceVO。
VmInstanceVO被导入但未在代码中使用,实际引用的是VmInstanceEO。建议移除此冗余导入。♻️ 建议的修复
import org.zstack.header.managementnode.ManagementNodeVO; import org.zstack.header.vm.VmInstanceEO; -import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vo.ForeignKey;🤖 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/VmMetadataDirtyVO.java` at line 5, The file VmMetadataDirtyVO imports VmInstanceVO but actually uses VmInstanceEO; remove the unused import statement for VmInstanceVO in VmMetadataDirtyVO to eliminate the redundant import and rely on the existing reference to VmInstanceEO (no other code changes required).sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java (1)
28-35: 为参数List补全泛型,避免弱类型输入。Line [29]/[32]/[35] 当前是原始
java.util.List,会丢失编译期约束。这里建议改为List<String>,与 VM 元数据 API 契约保持一致。建议修改
- 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/UpdateVmInstanceMetadataAction.java` around lines 28 - 35, Update the three raw List fields in UpdateVmInstanceMetadataAction to use generics (List<String>) to enforce compile-time type safety and match the VM metadata API contract: change vmUuids, systemTags, and userTags from java.util.List to java.util.List<String>, and update any related imports or usages that rely on raw types to the parameterized type.compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
123-124: GC 间隔配置值缺少防御性处理。
VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class)如果配置值无效或为 null,可能导致异常。建议添加防御性处理:♻️ 建议添加默认值保护
- long gcIntervalSec = TimeUnit.HOURS.toSeconds(VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class)); + Long intervalHours = VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class); + long gcIntervalSec = TimeUnit.HOURS.toSeconds(intervalHours != null ? intervalHours : 1L);🤖 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 123 - 124, VmExpungeMetadataFlow currently reads VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class) directly into gcIntervalSec and passes it to gc.deduplicateSubmit, which can throw if the config is null/invalid or non-positive; change the flow to defensively obtain the config: call VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class) into a local Long, check for null or <=0 and fall back to a sensible default (e.g., TimeUnit.HOURS.toSeconds(1L) or a defined constant), optionally clamp extremely large values, then compute gcIntervalSec from that safe value and pass the sanitized gcIntervalSec to gc.deduplicateSubmit to avoid NPEs or invalid scheduling.compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java (1)
1329-1352: 验证逻辑整体良好,有一个小的健壮性建议。新增的
validate(APIRegisterVmInstanceFromMetadataMsg)方法逻辑清晰,符合 ZStack API 拦截器的设计模式:
- 正确使用了
ApiMessageInterceptionException而非OperationFailureException- 采用了早返回模式,避免深层嵌套
但有一个潜在问题:当
psType为null时(第 1341 行),表示主存储记录可能不存在或类型字段为空。此时抛出的异常消息"primary storage[uuid:%s, type:%s] does not support vm metadata"可能会误导用户,因为实际原因是主存储不存在。♻️ 建议区分主存储不存在与不支持元数据的场景
String psType = Q.New(PrimaryStorageVO.class) .select(PrimaryStorageVO_.type) .eq(PrimaryStorageVO_.uuid, psUuid) .findValue(); + if (psType == null) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s] not found", psUuid)); + } + VmMetadataPathBuildExtensionPoint ext = (psType != null) ? pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class) : null; if (ext == null) { throw new ApiMessageInterceptionException(argerr( "primary storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType)); }🤖 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/VmInstanceApiInterceptor.java` around lines 1329 - 1352, The current validate(APIRegisterVmInstanceFromMetadataMsg) treats a null psType the same as "storage does not support vm metadata" which is misleading when the primary storage record is missing; change the logic to first check existence of the PrimaryStorageVO for psUuid (e.g. query for uuid or use an existence check on PrimaryStorageVO), and if the record is missing throw an ApiMessageInterceptionException with argerr("primary storage[uuid:%s] not found", psUuid); only when the record exists but psType is present and no extension is found should you throw the existing "does not support vm metadata" message; update the branches around psType, ext and the error handling in validate(APIRegisterVmInstanceFromMetadataMsg) 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
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 41-46: When building the
CleanupVmInstanceMetadataOnPrimaryStorageMsg in triggerNow(), ensure hostUuid
has a fallback: if hostUuid is null (inherited from VmExpungeMetadataFlow) query
for an available host on the given primaryStorageUuid that has an active
agent/connection and can reach the primary (using existing host query utilities
or QueryHostMsg), set that host's UUID into
CleanupVmInstanceMetadataOnPrimaryStorageMsg.setHostUuid(...) before sending,
and if none found fail fast with a clear error so GC retry won't repeatedly fail
due to message routing.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Around line 24-25: The field dirtyVersion in class VmMetadataDirtyVO currently
uses Java's primitive long which defaults to 0, conflicting with the DB schema
default of 1; update the entity so new instances get 1 by either initializing
the field (private long dirtyVersion = 1) or setting it in a `@PrePersist` method
(e.g., ensure dirtyVersion is set to 1 if <= 0) to match the SQL DEFAULT and
preserve correct dirty-version semantics.
In
`@sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java`:
- Around line 28-29: The uuid field in
ScanVmInstanceMetadataFromPrimaryStorageAction is marked required=true but
currently allows empty strings; update the `@Param` annotation on the public
java.lang.String uuid field so emptyString=false and nonempty=true (i.e.,
require a non-empty, non-blank uuid) to enable fail-fast validation; keep other
flags (required, nullElements, noTrim) as appropriate.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 89-93: The flow currently sets a possibly-null hostUuid on
CleanupVmInstanceMetadataOnPrimaryStorageMsg (in VmExpungeMetadataFlow by using
spec.getVmInventory().getHostUuid() / getLastHostUuid()), which causes GC
routing to fail when both are null; instead, remove assigning a null hostUuid
here and implement host selection either by (A) querying
PrimaryStorageClusterRefVO + HostVO to pick any available host in the primary
storage's clusters and set that hostUuid before sending the message, or (B)
leave hostUuid unset and add selection logic in the
CleanupVmInstanceMetadataOnPrimaryStorageMsg handler to lookup
PrimaryStorageClusterRefVO and HostVO and choose a live host at runtime; update
VmExpungeMetadataFlow (the code that constructs cmsg) and/or the handler to
perform the DB query and set a non-null hostUuid only when an actual host is
found.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3924-3928: The handle method should guard against a null/empty
hostUuid before calling httpCall: in
LocalStorageKvmBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg,
String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>) validate
msg.getMetadataPath() and hostUuid (the latter must not be null/empty) and if
hostUuid is missing immediately complete with an error via the provided
completion (e.g., completion.fail(...) or construct a failed
CleanupVmInstanceMetadataOnPrimaryStorageReply), otherwise proceed to create
CleanupVmMetadataCmd and call httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, ...).
Ensure the error message clearly states hostUuid is empty so routing is not
attempted.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 337-372: The simulator's metadata lifecycle is broken:
writeVmMetadata() and cleanupVmMetadata() only record commands while
getVmInstanceMetadata() and scanVmMetadata() always return empty responses; fix
this in LocalStorageSimulator by maintaining an in-memory metadata store keyed
by VM/volume IDs (e.g., add a Map in the simulator or extend config to hold
metadata entries), update writeVmMetadata() to persist the metadata into that
store when handling WriteVmMetadataCmd (in addition to appending to
config.writeVmMetadataCmds), update getVmInstanceMetadata() to read from the
store and populate GetVmInstanceMetadataRsp, update scanVmMetadata() to return
matching entries from the store into ScanVmMetadataRsp, and update
cleanupVmMetadata() to remove entries from the store when handling
CleanupVmMetadataCmd (while still recording config.cleanupVmMetadataCmds);
ensure the response objects mirror the stored data so tests see realistic
read/scan/cleanup behavior.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1935-1944: 当前并发控制以 primary storage 为粒度(getSyncSignature() 返回
"update-metadata-on-ps-"+self.getUuid())但 getSyncLevel() 允许 10 个并发,导致同一
metadataPath 的并发写入会覆盖或半写;在提交 thdf.chainSubmit(new ChainTask(msg) { ... }) 时,将
getSyncSignature() 收敛到单个 metadata 文件(例如包含 metadataPath 或 metadata 文件名/UUID 而非仅
self.getUuid()),并把 getSyncLevel() 返回值改为 1,以确保对同一 metadataPath 的更新串行化(参考
ChainTask, thdf.chainSubmit, getSyncSignature(), getSyncLevel(), metadataPath)。
- Around line 2038-2060: handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)
currently calls backend.handle(...) directly, bypassing the serialization used
by UpdateVmInstanceMetadataOnPrimaryStorageMsg and risking races; change it to
compute the same sync key (the metadataPath/key used by
UpdateVmInstanceMetadataOnPrimaryStorageMsg) and submit a ChainTask (using the
same chain/queue mechanism) that runs the backend.handle(...) inside its run()
so cleanup executes serialized with update/register operations; keep existing
Reply/ReturnValueCompletion semantics (use the same reply object and error
handling) and still obtain the hostUuid via
factory.getConnectedHostForOperation/getBackendByHostUuid before invoking the
chained task.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2089-2095: The write/delete metadata success branches return
without refreshing primary storage capacity; after receiving a successful
WriteVmMetadataRsp (from KVMHostAsyncHttpCallReply) you must call
updatePrimaryStorageCapacity(...) to refresh NFS usage before calling
completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); locate
the success branches handling WriteVmMetadataRsp (and the analogous branch
around lines 2186-2193) and insert a call to updatePrimaryStorageCapacity with
the host/primary storage identifiers and a callback that then calls
completion.success(...) (or completion.fail(...) on error) so capacity is
updated immediately after metadata create/delete.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1889-1892: The filtered metadata currently only excludes
VmMetadataCategory.TEMPLATE_CACHE, but TEMPLATE entries still pass through;
update the filtering in PrimaryStorageBase (the stream that produces 'filtered'
before evt.setVmInstanceMetadata) to whitelist only VmMetadataCategory.REGULAR
(or explicitly exclude both TEMPLATE and TEMPLATE_CACHE) so
evt.setVmInstanceMetadata receives pure VM metadata; adjust the lambda that
checks e.getVmCategory() accordingly.
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 123-124: VmExpungeMetadataFlow currently reads
VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class) directly into
gcIntervalSec and passes it to gc.deduplicateSubmit, which can throw if the
config is null/invalid or non-positive; change the flow to defensively obtain
the config: call
VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class) into a local
Long, check for null or <=0 and fall back to a sensible default (e.g.,
TimeUnit.HOURS.toSeconds(1L) or a defined constant), optionally clamp extremely
large values, then compute gcIntervalSec from that safe value and pass the
sanitized gcIntervalSec to gc.deduplicateSubmit to avoid NPEs or invalid
scheduling.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Around line 1329-1352: The current
validate(APIRegisterVmInstanceFromMetadataMsg) treats a null psType the same as
"storage does not support vm metadata" which is misleading when the primary
storage record is missing; change the logic to first check existence of the
PrimaryStorageVO for psUuid (e.g. query for uuid or use an existence check on
PrimaryStorageVO), and if the record is missing throw an
ApiMessageInterceptionException with argerr("primary storage[uuid:%s] not
found", psUuid); only when the record exists but psType is present and no
extension is found should you throw the existing "does not support vm metadata"
message; update the branches around psType, ext and the error handling in
validate(APIRegisterVmInstanceFromMetadataMsg) accordingly.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Line 5: The file VmMetadataDirtyVO imports VmInstanceVO but actually uses
VmInstanceEO; remove the unused import statement for VmInstanceVO in
VmMetadataDirtyVO to eliminate the redundant import and rely on the existing
reference to VmInstanceEO (no other code changes required).
In
`@sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java`:
- Around line 1-6: Remove the redundant same-package wildcard import in
GetVmInstanceMetadataFromPrimaryStorageAction: the class is already declared in
package org.zstack.sdk so delete the line "import org.zstack.sdk.*;" and keep
only the necessary imports (e.g., java.util.HashMap, java.util.Map) so classes
in org.zstack.sdk are accessed directly without that import.
- Around line 31-35: 在 GetVmInstanceMetadataFromPrimaryStorageAction
类中,systemTags 和 userTags 字段目前使用原始类型 java.util.List;请改为使用带泛型的类型(例如
java.util.List<String> 或其他合适的元素类型)以提升类型安全性,并更新相应的声明(字段名
systemTags、userTags)及必要的导入/序列化注解匹配。
In `@sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java`:
- Around line 28-35: Update the three raw List fields in
UpdateVmInstanceMetadataAction to use generics (List<String>) to enforce
compile-time type safety and match the VM metadata API contract: change vmUuids,
systemTags, and userTags from java.util.List to java.util.List<String>, and
update any related imports or usages that rely on raw types to the parameterized
type.
🪄 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: 5d62f588-e824-4a5b-a724-840e0e038a01
⛔ 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 (144)
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/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/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/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/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 (95)
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- sdk/src/main/java/SourceClassMap.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
🚧 Files skipped from review as they are similar to previous changes (27)
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java
- testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
| CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | ||
| msg.setPrimaryStorageUuid(primaryStorageUuid); | ||
| msg.setVmUuid(vmUuid); | ||
| msg.setRootVolumeUuid(rootVolumeUuid); | ||
| msg.setMetadataPath(metadataPath); | ||
| msg.setHostUuid(hostUuid); |
There was a problem hiding this comment.
GC 执行时应实现 hostUuid 兜底逻辑。
当 hostUuid 为 null(从 VmExpungeMetadataFlow 继承而来)时,GC 重试也会因消息路由失败而无法完成。建议在 triggerNow() 中实现 host 动态选择:
♻️ 建议在 GC 执行时查找可用主机
+ String effectiveHostUuid = hostUuid;
+ if (effectiveHostUuid == null) {
+ // 通过 PrimaryStorageClusterRefVO 查找该主存储关联的集群,再选择一个可用主机
+ List<String> clusterUuids = Q.New(PrimaryStorageClusterRefVO.class)
+ .select(PrimaryStorageClusterRefVO_.clusterUuid)
+ .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, primaryStorageUuid)
+ .listValues();
+ if (!clusterUuids.isEmpty()) {
+ effectiveHostUuid = Q.New(HostVO.class)
+ .select(HostVO_.uuid)
+ .in(HostVO_.clusterUuid, clusterUuids)
+ .eq(HostVO_.state, HostState.Enabled)
+ .eq(HostVO_.status, HostStatus.Connected)
+ .limit(1)
+ .findValue();
+ }
+ if (effectiveHostUuid == null) {
+ logger.warn(String.format("[MetadataCleanupGC] no available host found for ps[uuid:%s], " +
+ "will retry later", primaryStorageUuid));
+ completion.fail(Platform.operr("no available host for metadata cleanup"));
+ return;
+ }
+ }
+
CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg();
msg.setPrimaryStorageUuid(primaryStorageUuid);
msg.setVmUuid(vmUuid);
msg.setRootVolumeUuid(rootVolumeUuid);
msg.setMetadataPath(metadataPath);
- msg.setHostUuid(hostUuid);
+ msg.setHostUuid(effectiveHostUuid);🤖 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 - 46, When building the
CleanupVmInstanceMetadataOnPrimaryStorageMsg in triggerNow(), ensure hostUuid
has a fallback: if hostUuid is null (inherited from VmExpungeMetadataFlow) query
for an available host on the given primaryStorageUuid that has an active
agent/connection and can reach the primary (using existing host query utilities
or QueryHostMsg), set that host's UUID into
CleanupVmInstanceMetadataOnPrimaryStorageMsg.setHostUuid(...) before sending,
and if none found fail fast with a clear error so GC retry won't repeatedly fail
due to message routing.
| @Column | ||
| private long dirtyVersion; |
There was a problem hiding this comment.
dirtyVersion 默认值与数据库 schema 不一致。
Java 原始类型 long 默认值为 0,但 SQL schema 定义 dirtyVersion BIGINT NOT NULL DEFAULT 1。当 JPA 持久化新实体时,会使用 Java 字段值 0 而非数据库默认值 1,可能导致脏版本追踪逻辑不一致。
建议在字段声明时显式初始化为 1,或在 @PrePersist 中设置。
🛠️ 建议的修复
`@Column`
- private long dirtyVersion;
+ private long dirtyVersion = 1;📝 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.
| @Column | |
| private long dirtyVersion; | |
| `@Column` | |
| private long dirtyVersion = 1; |
🤖 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/VmMetadataDirtyVO.java`
around lines 24 - 25, The field dirtyVersion in class VmMetadataDirtyVO
currently uses Java's primitive long which defaults to 0, conflicting with the
DB schema default of 1; update the entity so new instances get 1 by either
initializing the field (private long dirtyVersion = 1) or setting it in a
`@PrePersist` method (e.g., ensure dirtyVersion is set to 1 if <= 0) to match the
SQL DEFAULT and preserve correct dirty-version semantics.
| @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) | ||
| public java.lang.String uuid; |
There was a problem hiding this comment.
uuid 必填参数不应允许空字符串。
Line 28 的 @Param 当前配置为 required = true 但同时 emptyString = true、nonempty = false,这会放过 "",导致请求在更后层才失败,错误定位不友好。建议这里直接 fail-fast。
建议修改
- `@Param`(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ `@Param`(required = true, nonempty = true, nullElements = false, emptyString = false, noTrim = false)
public java.lang.String uuid;📝 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.
| @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) | |
| public java.lang.String uuid; | |
| `@Param`(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) | |
| public java.lang.String uuid; |
🤖 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/ScanVmInstanceMetadataFromPrimaryStorageAction.java`
around lines 28 - 29, The uuid field in
ScanVmInstanceMetadataFromPrimaryStorageAction is marked required=true but
currently allows empty strings; update the `@Param` annotation on the public
java.lang.String uuid field so emptyString=false and nonempty=true (i.e.,
require a non-empty, non-blank uuid) to enable fail-fast validation; keep other
flags (required, nullElements, noTrim) as appropriate.
146ae94 to
a86fd7a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (9)
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (2)
2157-2162:⚠️ Potential issue | 🟡 Minor扫描结果请始终返回空列表。
这里只有
rsp.metadataEntries != null才给 reply 赋值,结果会在“扫描成功但无结果”时暴露null。调用方一旦直接遍历getVmInstanceMetadata(),这里就会变成不必要的 NPE 风险。建议明确回Collections.emptyList()。🤖 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 - 2162, ScanVmInstanceMetadataFromPrimaryStorageReply may be returned with a null vmInstanceMetadata when rsp.metadataEntries is null, causing NPEs for callers; modify the block around ScanVmInstanceMetadataFromPrimaryStorageReply r so that you always call r.setVmInstanceMetadata(...) with an empty list when rsp.metadataEntries is null (e.g. Collections.emptyList()), and still set hostUuid on entries when present before calling completion.success(r); ensure use of ScanVmInstanceMetadataFromPrimaryStorageReply, rsp.metadataEntries, entry.setHostUuid(...), r.setVmInstanceMetadata(...), and completion.success(...) as the target symbols.
2089-2095:⚠️ Potential issue | 🟡 Minor写/删 metadata 成功后别漏掉容量刷新。
这两个 handler 没走下面通用的
asyncHttpCall(...)helper,所以成功分支也绕过了updatePrimaryStorageCapacity(...)。元数据文件的创建/删除后,主存容量会一直保留旧值,直到后续 ping/reconnect 才被动修正。Also applies to: 2186-2193
🤖 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 2089 - 2095, The success branches in NfsPrimaryStorageKVMBackend handling WriteVmMetadataRsp (method handling reply using WriteVmMetadataRsp) currently call completion.success(...) but skip the capacity refresh; after a successful write/delete response invoke updatePrimaryStorageCapacity(...) (the same routine used by asyncHttpCall-based handlers) to refresh the primary storage capacity for the affected primary storage/host before completing the operation so metadata file create/delete updates capacity immediately; apply the same change to the analogous handler around the WriteVmMetadataRsp logic at the 2186-2193 region.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (2)
1935-1944:⚠️ Potential issue | 🔴 Critical把 metadata 更新串行化收敛到 VM/metadataPath。
现在同步键只按 primary storage 分桶,且
getSyncLevel()允许 10 并发。同一台 VM / 同一个 metadata 文件的并发更新仍会同时落到同一个写路径上,最后一次写入会覆盖前一个结果。建议把 sync signature 至少细化到msg.getRootVolumeUuid()或msg.getMetadataPath(),并把 sync level 收到 1。🤖 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 1935 - 1944, The current ChainTask submitted via thdf.chainSubmit uses a sync signature only keyed by self.getUuid() and a parallelism of 10 (getSyncLevel returns 10), which allows concurrent updates to the same VM/metadata file and causes race overwrites; change ChainTask.getSyncSignature() to include a finer-grained identifier such as msg.getRootVolumeUuid() or msg.getMetadataPath() (e.g., "update-metadata-on-ps-" + self.getUuid() + "-" + msg.getRootVolumeUuid()) and reduce ChainTask.getSyncLevel() to 1 so updates for the same VM/metadataPath are serialized. Ensure you update the ChainTask implementation where thdf.chainSubmit(...) is invoked to reference these message fields.
2039-2060:⚠️ Potential issue | 🔴 Criticalcleanup 也要走和 update 相同的串行化通道。
这里直接选 host 并下发 backend,完全绕过了上面的
ChainTask。一旦 cleanup 和 update/register 并发命中同一个 metadata 文件,cleanup 可能删掉刚写好的内容,或者把正在进行的写操作打断。建议复用和 update 完全相同的 sync signature / sync level,把 cleanup 包进同一条串行化路径。🤖 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 2039 - 2060, The cleanup handler currently picks a host and calls backend.handle(...) directly, bypassing the ChainTask serialization used by update/register and risking races; change handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg) to submit the cleanup work as a ChainTask using the exact same sync signature and sync level used by the update/register path (so cleanup is enqueued on the same serialized queue), and move the logic that picks connectedHosts/getBackendByHostUuid and invokes backend.handle(...) into the ChainTask body; ensure the ChainTask reports success/failure back using the same ReturnValueCompletion/Reply plumbing as the update flow rather than replying directly outside the task.compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java (1)
41-46:⚠️ Potential issue | 🔴 CriticalGC 执行时不要盲用历史
hostUuid。
compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java:89-123里这个值最多只从hostUuid/lastHostUuid兜一层,仍可能为空;而且 GC 延迟执行时原 host 也可能已经不可用。这里直接透传给 cleanup 消息,会让依赖 host 执行清理的后端把一次可恢复的重试变成持续失败。建议在triggerNow()重新选择当前可用 host,或明确要求下游在msg.getHostUuid() == null时自行选 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/CleanupVmInstanceMetadataOnPrimaryStorageGC.java` around lines 41 - 46, The GC flow is passing a potentially stale or absent hostUuid into CleanupVmInstanceMetadataOnPrimaryStorageMsg; update triggerNow() in CleanupVmInstanceMetadataOnPrimaryStorageGC so it does not blindly forward the historical hostUuid from VmExpungeMetadataFlow: either (A) re-resolve a currently available host before calling msg.setHostUuid(...) (e.g., query available HostVOs for the primaryStorageUuid and pick a reachable host) or (B) explicitly clear the hostUuid (msg.setHostUuid(null)) so downstream cleanup handlers know to choose a host themselves when msg.getHostUuid() == null; adjust triggerNow() to implement one of these behaviors and add a short comment referencing CleanupVmInstanceMetadataOnPrimaryStorageMsg to document the choice.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)
346-363:⚠️ Potential issue | 🟡 Minorsimulator 需要回填读取/扫描结果。
现在
getVmInstanceMetadata()/scanVmMetadata()只记录了 command,成功分支却返回空rsp。这样新增用例最多只能断言“接口被调用”,无法覆盖上层对 metadata 内容的解析和聚合逻辑。至少需要把预置的 metadata 结果填回这两个响应对象。🤖 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 346 - 363, The simulator handlers getVmInstanceMetadata and scanVmMetadata currently record the incoming commands but return empty GetVmInstanceMetadataRsp/ScanVmMetadataRsp objects; change both to populate their response objects with preconfigured metadata from the simulator config (e.g., a map/list of preloaded metadata entries) that matches the incoming GetVmInstanceMetadataCmd/ScanVmMetadataCmd (lookup by VM UUID or scan path), assign the matching metadata into the appropriate fields on GetVmInstanceMetadataRsp and ScanVmMetadataRsp, then call reply(entity, rsp); ensure you reference the existing symbols GetVmInstanceMetadataCmd, ScanVmMetadataCmd, GetVmInstanceMetadataRsp, ScanVmMetadataRsp, config.getVmInstanceMetadataCmds and config.scanVmMetadataCmds when implementing the lookup and response population.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (2)
3413-3437:⚠️ Potential issue | 🔴 Critical失效
hostUuid的回退/终态处理前一轮提过,这里还是原样。
get路径仍然只有在hostUuid == null时才回退到rootVolumeUuid;cleanup路径也没有把“host 已删除/解绑”当成终态。卷迁移后会读错 host,VM 已 expunge 的清理则会继续失败并把 GC 拖成持续重试。Also applies to: 3509-3527
🤖 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 3413 - 3437, The code only falls back to rootVolumeUuid when msg.getHostUuid() is null but still treats failures from getHostUuidByResourceUuid(...) as transient errors; change this to treat "host not found / host unbound / resource deleted" as a terminal/no-op for both the get path (around hostUuid/getHostUuidByResourceUuid and bkd.handle(...)) and the cleanup path (the similar block at lines 3509-3527): detect when getHostUuidByResourceUuid returns null or throws an exception that indicates the resource/host is deleted/unbound, and in that case short-circuit by returning the appropriate terminal reply (no-op success or explicit terminal error as your codebase expects) instead of leaving it to retry/requeue; ensure both the get handler and the cleanup handler use the same terminal-handling logic.
3358-3367:⚠️ Potential issue | 🔴 Critical这个 metadata 写入并发问题前一轮已经提过,这里还没收住。
getSyncSignature()还是只按 PS 维度分桶,而getSyncLevel()仍然放到 10;同一 VM/root volume 的 metadata 更新会并发落到同一块 local PS,上一次写很容易被后一次覆盖或乱序。🤖 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 3358 - 3367, 当前的 ChainTask 在 thdf.chainSubmit(...) 中的 getSyncSignature() 仅按主存储(self.getUuid())分桶且 getSyncLevel() 返回 10,会导致同一 VM/根卷(root volume)在同一 PS 上的 metadata 更新并发并发生覆盖或乱序;请把 getSyncSignature() 扩展为包含更细粒度的标识(例如在 "update-metadata-on-ps-" + self.getUuid() 后追加 rootVolumeUuid 或 vmUuid/volumeUuid)以按 VM/卷级别分桶,并将 getSyncLevel() 从 10 调整为更严格的级别(例如 1)以强制序列化这些更新;在 ChainTask 的 getSyncSignature() 与 getSyncLevel() 实现处做相应修改以避免并发写覆盖。plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3924-3929:⚠️ Potential issue | 🔴 Critical清理元数据前仍缺少
hostUuid兜底或前置校验。Line 3928 仍然直接把
hostUuid传给httpCall(...)。在 VM expunge 的 GC fallback 场景里,这个值可能为空;一旦为空,主机路由会直接失败,导致元数据清理根本不会下发。这里至少要先 fail-fast,更稳妥的是在发送前补一个 host 解析兜底。🔧 最小修复
`@Override` void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { + if (StringUtils.isBlank(hostUuid)) { + completion.fail(operr("cleanup vm metadata failed: hostUuid is null, metadataPath=%s", msg.getMetadataPath())); + return; + } + CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); cmd.metadataPath = msg.getMetadataPath(); httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion<CleanupVmMetadataRsp>(completion) {🤖 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 - 3929, The handler method handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) must guard against a null hostUuid before calling httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,...); either fail-fast by calling completion.fail(...) with a clear error when hostUuid is null, or resolve a fallback host (e.g. look up a connected host for the primary storage using msg.getPrimaryStorageUuid() or a HostFinder/DAO) and set hostUuid to that value before invoking httpCall; ensure the error path uses the same completion to return failure and the resolved-path passes the non-null hostUuid into httpCall.
🧹 Nitpick comments (3)
sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java (1)
28-35: 原始类型List缺少泛型参数。
vmUuids、systemTags和userTags字段使用了原始的java.util.List类型而不是List<String>。虽然这在 ZStack SDK 自动生成的代码中是常见模式,但会产生编译器警告。♻️ 如果手动维护此文件,建议添加泛型参数
- 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/UpdateVmInstanceMetadataAction.java` around lines 28 - 35, The fields vmUuids, systemTags, and userTags in class UpdateVmInstanceMetadataAction are declared as raw java.util.List which causes compiler warnings; change their types to java.util.List<String> (i.e., replace the raw types for vmUuids, systemTags, and userTags with generic List<String>) while keeping the existing `@Param` annotations and other code unchanged so the signatures are type-safe and warnings are eliminated.header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java (1)
15-26:extractOldPrefix方法逻辑正确,建议添加单元测试。该方法从路径中提取存储基础目录前缀(如从
/mnt/ps/rootVolumes/...中提取/mnt/ps/)。逻辑正确,但由于涉及字符串索引计算,建议为该方法添加单元测试以覆盖各种边界情况,例如:
- 路径不包含任何 marker
- 路径包含多个 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 - 26, Add unit tests for VmInstanceMetadataConstants.extractOldPrefix to cover null and non-leading-slash inputs, paths that do not contain any STORAGE_PATH_MARKERS, paths containing multiple markers (ensure the first marker is used and returned prefix ends with '/'), and a path where a marker is at the very beginning; use the actual STORAGE_PATH_MARKERS constant in tests and assert expected substring results (including null returns) to validate the index/substr logic and boundary conditions.compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java (1)
1329-1352: 验证逻辑实现正确,但错误消息可以更清晰。当
primaryStorageUuid在数据库中不存在时,psType为 null,错误消息会显示type:null,这对用户排查问题不够友好。建议在 extension point 检查失败时区分"主存储不存在"和"主存储类型不支持元数据"两种情况。♻️ 建议改进错误消息
String psType = Q.New(PrimaryStorageVO.class) .select(PrimaryStorageVO_.type) .eq(PrimaryStorageVO_.uuid, psUuid) .findValue(); + if (psType == null) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s] not found", psUuid)); + } VmMetadataPathBuildExtensionPoint ext = (psType != null) ? pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class) : null; if (ext == null) { throw new ApiMessageInterceptionException(argerr( - "primary storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType)); + "primary storage[uuid:%s, type:%s] does not support vm metadata registration", psUuid, psType)); }🤖 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/VmInstanceApiInterceptor.java` around lines 1329 - 1352, In validate(APIRegisterVmInstanceFromMetadataMsg msg) improve error clarity by first checking whether a PrimaryStorageVO exists for psUuid (query for existence or select uuid) and throw ApiMessageInterceptionException(argerr("primary storage[uuid:%s] not found", psUuid)) if missing; only then obtain psType and call pluginRgty.getExtensionFromMap(...) for VmMetadataPathBuildExtensionPoint, and if ext is null throw ApiMessageInterceptionException(argerr("primary storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType)); keep the existing ext.validateMetadataPath(psUuid, path) behavior for path-specific errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@conf/db/upgrade/V5.0.0__schema.sql`:
- Around line 9-10: The timestamp columns lastOpDate and createDate are
missing/using a sentinel default; update the schema so lastOpDate includes a
DEFAULT (e.g., DEFAULT CURRENT_TIMESTAMP) in addition to ON UPDATE
CURRENT_TIMESTAMP and change createDate to DEFAULT CURRENT_TIMESTAMP if you
intend to record actual creation time; if the sentinel '1999-12-31 23:59:59' is
intentional, add a comment near the column definitions clarifying that
createDate uses a sentinel value and leave it as-is.
---
Duplicate comments:
In
`@compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java`:
- Around line 41-46: The GC flow is passing a potentially stale or absent
hostUuid into CleanupVmInstanceMetadataOnPrimaryStorageMsg; update triggerNow()
in CleanupVmInstanceMetadataOnPrimaryStorageGC so it does not blindly forward
the historical hostUuid from VmExpungeMetadataFlow: either (A) re-resolve a
currently available host before calling msg.setHostUuid(...) (e.g., query
available HostVOs for the primaryStorageUuid and pick a reachable host) or (B)
explicitly clear the hostUuid (msg.setHostUuid(null)) so downstream cleanup
handlers know to choose a host themselves when msg.getHostUuid() == null; adjust
triggerNow() to implement one of these behaviors and add a short comment
referencing CleanupVmInstanceMetadataOnPrimaryStorageMsg to document the choice.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3413-3437: The code only falls back to rootVolumeUuid when
msg.getHostUuid() is null but still treats failures from
getHostUuidByResourceUuid(...) as transient errors; change this to treat "host
not found / host unbound / resource deleted" as a terminal/no-op for both the
get path (around hostUuid/getHostUuidByResourceUuid and bkd.handle(...)) and the
cleanup path (the similar block at lines 3509-3527): detect when
getHostUuidByResourceUuid returns null or throws an exception that indicates the
resource/host is deleted/unbound, and in that case short-circuit by returning
the appropriate terminal reply (no-op success or explicit terminal error as your
codebase expects) instead of leaving it to retry/requeue; ensure both the get
handler and the cleanup handler use the same terminal-handling logic.
- Around line 3358-3367: 当前的 ChainTask 在 thdf.chainSubmit(...) 中的
getSyncSignature() 仅按主存储(self.getUuid())分桶且 getSyncLevel() 返回 10,会导致同一
VM/根卷(root volume)在同一 PS 上的 metadata 更新并发并发生覆盖或乱序;请把 getSyncSignature()
扩展为包含更细粒度的标识(例如在 "update-metadata-on-ps-" + self.getUuid() 后追加 rootVolumeUuid 或
vmUuid/volumeUuid)以按 VM/卷级别分桶,并将 getSyncLevel() 从 10 调整为更严格的级别(例如 1)以强制序列化这些更新;在
ChainTask 的 getSyncSignature() 与 getSyncLevel() 实现处做相应修改以避免并发写覆盖。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3924-3929: The handler method
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply>
completion) must guard against a null hostUuid before calling
httpCall(CLEANUP_VM_METADATA_PATH, hostUuid,...); either fail-fast by calling
completion.fail(...) with a clear error when hostUuid is null, or resolve a
fallback host (e.g. look up a connected host for the primary storage using
msg.getPrimaryStorageUuid() or a HostFinder/DAO) and set hostUuid to that value
before invoking httpCall; ensure the error path uses the same completion to
return failure and the resolved-path passes the non-null hostUuid into httpCall.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 346-363: The simulator handlers getVmInstanceMetadata and
scanVmMetadata currently record the incoming commands but return empty
GetVmInstanceMetadataRsp/ScanVmMetadataRsp objects; change both to populate
their response objects with preconfigured metadata from the simulator config
(e.g., a map/list of preloaded metadata entries) that matches the incoming
GetVmInstanceMetadataCmd/ScanVmMetadataCmd (lookup by VM UUID or scan path),
assign the matching metadata into the appropriate fields on
GetVmInstanceMetadataRsp and ScanVmMetadataRsp, then call reply(entity, rsp);
ensure you reference the existing symbols GetVmInstanceMetadataCmd,
ScanVmMetadataCmd, GetVmInstanceMetadataRsp, ScanVmMetadataRsp,
config.getVmInstanceMetadataCmds and config.scanVmMetadataCmds when implementing
the lookup and response population.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1935-1944: The current ChainTask submitted via thdf.chainSubmit
uses a sync signature only keyed by self.getUuid() and a parallelism of 10
(getSyncLevel returns 10), which allows concurrent updates to the same
VM/metadata file and causes race overwrites; change ChainTask.getSyncSignature()
to include a finer-grained identifier such as msg.getRootVolumeUuid() or
msg.getMetadataPath() (e.g., "update-metadata-on-ps-" + self.getUuid() + "-" +
msg.getRootVolumeUuid()) and reduce ChainTask.getSyncLevel() to 1 so updates for
the same VM/metadataPath are serialized. Ensure you update the ChainTask
implementation where thdf.chainSubmit(...) is invoked to reference these message
fields.
- Around line 2039-2060: The cleanup handler currently picks a host and calls
backend.handle(...) directly, bypassing the ChainTask serialization used by
update/register and risking races; change
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg) to submit the cleanup work
as a ChainTask using the exact same sync signature and sync level used by the
update/register path (so cleanup is enqueued on the same serialized queue), and
move the logic that picks connectedHosts/getBackendByHostUuid and invokes
backend.handle(...) into the ChainTask body; ensure the ChainTask reports
success/failure back using the same ReturnValueCompletion/Reply plumbing as the
update flow rather than replying directly outside the task.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2157-2162: ScanVmInstanceMetadataFromPrimaryStorageReply may be
returned with a null vmInstanceMetadata when rsp.metadataEntries is null,
causing NPEs for callers; modify the block around
ScanVmInstanceMetadataFromPrimaryStorageReply r so that you always call
r.setVmInstanceMetadata(...) with an empty list when rsp.metadataEntries is null
(e.g. Collections.emptyList()), and still set hostUuid on entries when present
before calling completion.success(r); ensure use of
ScanVmInstanceMetadataFromPrimaryStorageReply, rsp.metadataEntries,
entry.setHostUuid(...), r.setVmInstanceMetadata(...), and
completion.success(...) as the target symbols.
- Around line 2089-2095: The success branches in NfsPrimaryStorageKVMBackend
handling WriteVmMetadataRsp (method handling reply using WriteVmMetadataRsp)
currently call completion.success(...) but skip the capacity refresh; after a
successful write/delete response invoke updatePrimaryStorageCapacity(...) (the
same routine used by asyncHttpCall-based handlers) to refresh the primary
storage capacity for the affected primary storage/host before completing the
operation so metadata file create/delete updates capacity immediately; apply the
same change to the analogous handler around the WriteVmMetadataRsp logic at the
2186-2193 region.
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java`:
- Around line 1329-1352: In validate(APIRegisterVmInstanceFromMetadataMsg msg)
improve error clarity by first checking whether a PrimaryStorageVO exists for
psUuid (query for existence or select uuid) and throw
ApiMessageInterceptionException(argerr("primary storage[uuid:%s] not found",
psUuid)) if missing; only then obtain psType and call
pluginRgty.getExtensionFromMap(...) for VmMetadataPathBuildExtensionPoint, and
if ext is null throw ApiMessageInterceptionException(argerr("primary
storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType)); keep
the existing ext.validateMetadataPath(psUuid, path) behavior for path-specific
errors.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java`:
- Around line 15-26: Add unit tests for
VmInstanceMetadataConstants.extractOldPrefix to cover null and non-leading-slash
inputs, paths that do not contain any STORAGE_PATH_MARKERS, paths containing
multiple markers (ensure the first marker is used and returned prefix ends with
'/'), and a path where a marker is at the very beginning; use the actual
STORAGE_PATH_MARKERS constant in tests and assert expected substring results
(including null returns) to validate the index/substr logic and boundary
conditions.
In `@sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java`:
- Around line 28-35: The fields vmUuids, systemTags, and userTags in class
UpdateVmInstanceMetadataAction are declared as raw java.util.List which causes
compiler warnings; change their types to java.util.List<String> (i.e., replace
the raw types for vmUuids, systemTags, and userTags with generic List<String>)
while keeping the existing `@Param` annotations and other code unchanged so the
signatures are type-safe and warnings are eliminated.
🪄 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: 0f591b0c-697a-4b75-90dc-ffb19e0ca16c
⛔ 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 (144)
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/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/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/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/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 (93)
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java
- header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java
- header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java
- header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
- header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- 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/APISetVmBootOrderMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java
- header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java
- header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java
- compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java
- header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java
- header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- sdk/src/main/java/SourceClassMap.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java
🚧 Files skipped from review as they are similar to previous changes (24)
- 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/volume/APIDeleteDataVolumeMsg.java
- header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java
- header/src/main/java/org/zstack/header/vm/VmInstanceState.java
- resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java
- header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java
- header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
| `lastOpDate` timestamp on update CURRENT_TIMESTAMP, | ||
| `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', |
There was a problem hiding this comment.
lastOpDate 和 createDate 的默认值需要确认。
lastOpDate仅有on update CURRENT_TIMESTAMP,缺少DEFAULT子句。首次 INSERT 时该列将为 NULL,直到发生 UPDATE 才会有值。createDate使用'1999-12-31 23:59:59'作为默认值而非CURRENT_TIMESTAMP,这是一个异常的占位符日期。
建议确认这是否为预期的 sentinel 值模式。如果需要记录实际创建时间,应使用 DEFAULT CURRENT_TIMESTAMP。
💡 如果需要修复时间戳默认值
- `lastOpDate` timestamp on update CURRENT_TIMESTAMP,
- `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59',
+ `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `createDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,📝 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.
| `lastOpDate` timestamp on update CURRENT_TIMESTAMP, | |
| `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', | |
| `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |
| `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', |
🤖 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, The timestamp
columns lastOpDate and createDate are missing/using a sentinel default; update
the schema so lastOpDate includes a DEFAULT (e.g., DEFAULT CURRENT_TIMESTAMP) in
addition to ON UPDATE CURRENT_TIMESTAMP and change createDate to DEFAULT
CURRENT_TIMESTAMP if you intend to record actual creation time; if the sentinel
'1999-12-31 23:59:59' is intentional, add a comment near the column definitions
clarifying that createDate uses a sentinel value and leave it as-is.
a86fd7a to
9dce3f2
Compare
APIImpact Resolves: ZSV-11559 Change-Id: I6b6a6378627264646d6a76726762736e77787373
9dce3f2 to
ec40b4b
Compare
APIImpact
Resolves: ZSV-11559
Change-Id: I6b6a6378627264646d6a76726762736e77787373
sync from gitlab !9469