<feature>[vm]: add vm metadata header definitions#3781
<feature>[vm]: add vm metadata header definitions#3781zstack-robot-1 wants to merge 7 commits intozsv_5.0.0from
Conversation
Resolves: ZSV-11559 Change-Id: I3cd9fa26b6af7bd47787ab88c5050683a3356059
Resolves: ZSV-11559 Change-Id: I91c9593bfced1a4c323841f275a8164d3373e133
Resolves: ZSV-11559 Change-Id: I4ad67f16ba0a37ec0f5add571f4f74752bcfbee5
Resolves: ZSV-11559 Change-Id: Idc34906c7c767f5fdf5213d751b293ec951a9b31
Resolves: ZSV-11559 Change-Id: Ie71ae4242af1031164e34d3f55bf8d379a9f74ec
Resolves: ZSV-11771 Change-Id: I801a5f4d8ba84e47a642be4043043af88979dd4e
Let APIScanVmInstanceMetadataFromPrimaryStorageMsg extend APISyncCallMessage APIImpact Resolves: ZSV-11790 Change-Id: I6b796372747178757767646b77676d646361636f
Walkthrough新增VM实例元数据管理系统,支持元数据持久化、清理、扫描、注册等操作。包括REST API端点、存储后端支持、全局配置参数、数据库表和SDK客户端库。 Changes
Sequence DiagramsequenceDiagram
participant Client as 客户端
participant API as API层<br/>(PrimaryStorageBase)
participant PS as 主存储服务<br/>(LocalStorage/NFS)
participant Host as 物理主机<br/>(KVM)
participant DB as 数据库<br/>(元数据表)
Client->>API: APIScanVmInstanceMetadataFromPrimaryStorageMsg
activate API
API->>API: 验证存储状态、类型、扩展点
API->>PS: ScanVmInstanceMetadataFromPrimaryStorageMsg
activate PS
PS->>PS: 获取连接的主机列表
PS->>Host: 扫描元数据目录
activate Host
Host->>Host: 读取元数据文件
Host-->>PS: 返回元数据扫描结果
deactivate Host
PS->>PS: 聚合扫描结果、过滤
PS-->>API: ScanVmInstanceMetadataFromPrimaryStorageReply
deactivate PS
API-->>Client: APIScanVmInstanceMetadataFromPrimaryStorageReply
deactivate API
sequenceDiagram
participant VM as VM生命周期
participant Flow as VmExpungeMetadataFlow<br/>(驱逐流程)
participant PS as 主存储
participant Host as 物理主机
participant GC as CleanupVmInstanceMetadataOnPrimaryStorageGC<br/>(垃圾收集)
participant DB as 数据库
VM->>Flow: 开始VM驱逐
activate Flow
Flow->>Flow: 解析存储类型、构建元数据路径
Flow->>PS: CleanupVmInstanceMetadataOnPrimaryStorageMsg
activate PS
PS->>Host: 执行清理操作
Host->>Host: 删除元数据文件
Host-->>PS: 返回成功
PS-->>Flow: 返回成功
deactivate PS
Flow->>DB: 记录清理完成
Flow-->>VM: 流程继续
deactivate Flow
alt 清理失败
Flow->>GC: 提交定时重试任务
activate GC
GC->>DB: 查询清理状态、重试
GC->>PS: 重新执行清理
GC-->>DB: 更新重试状态
deactivate GC
end
代码审查工作量🎯 4 (复杂) | ⏱️ ~60 分钟 Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (12)
header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java (1)
6-13: 建议统一元数据字段命名以降低心智负担。这一处使用
vmMetadata,而同批新回复类多为metadata。建议统一命名风格,减少后续 DTO 转换和维护成本。可选重构示例
public class ReadVmInstanceMetadataReply extends MessageReply { - private String vmMetadata; + private String metadata; - public String getVmMetadata() { - return vmMetadata; + public String getMetadata() { + return metadata; } - public void setVmMetadata(String vmMetadata) { - this.vmMetadata = vmMetadata; + public void setMetadata(String metadata) { + this.metadata = metadata; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java` around lines 6 - 13, The vmMetadata field and its accessors in ReadVmInstanceMetadataReply are named inconsistently with other new reply DTOs that use metadata; rename the private String vmMetadata, getVmMetadata(), and setVmMetadata(...) to private String metadata, getMetadata(), and setMetadata(...) in class ReadVmInstanceMetadataReply, update all internal usages and any (de)serialization references or tests that refer to vmMetadata, and run/adjust any DTO mapping code that expects the previous name to ensure backward-compatible JSON/XML field names if needed (e.g., add `@SerializedName/`@JsonProperty("vmMetadata") only if preserving external contract).header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java (1)
20-28: 建议避免在公共接口中使用布尔标志参数。
markDirty(String, boolean)在调用侧会退化成markDirty(uuid, true/false),语义不够直观。建议改为枚举或拆分方法名表达意图。可选重构示例
public interface VmMetadataDirtyService { + enum DirtyReason { + CONFIG_CHANGE, + STORAGE_STRUCTURE_CHANGE + } + boolean markDirty(String vmInstanceUuid); - boolean markDirty(String vmInstanceUuid, boolean storageStructureChange); + boolean markDirty(String vmInstanceUuid, DirtyReason reason); }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/VmMetadataDirtyService.java` around lines 20 - 28, The public API VmMetadataDirtyService.markDirty(String vmInstanceUuid, boolean storageStructureChange) should avoid a boolean flag parameter; update the API to make intent explicit by either introducing an enum ChangeType (e.g., ChangeType.STORAGE_STRUCTURE vs ChangeType.METADATA_ONLY) and replacing the boolean with ChangeType, or by splitting into two descriptive methods such as markDirty(String vmInstanceUuid) and markDirtyWithStorageStructureChange(String vmInstanceUuid); update all implementations and call sites that reference markDirty to use the new enum or the two new methods and adjust javadoc/comments for the chosen approach.header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java (1)
9-13: 可选优化:先做空入参判断,再分配结果列表。当前实现每次都会先创建
ArrayList,可小幅减少无效分配。参考修改
default List<String> batchResolveVmUuids(List<String> fieldValues) { - List<String> result = new ArrayList<>(); if (fieldValues == null || fieldValues.isEmpty()) { - return result; + return new ArrayList<>(); } + List<String> result = new ArrayList<>(); for (String v : fieldValues) { if (v == null) { continue; }🤖 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 - 13, In VmUuidFromApiResolver.batchResolveVmUuids, avoid allocating the result ArrayList before validating input; move the null/empty check for fieldValues to the top and only create and return the result list after confirming fieldValues is non-null and non-empty so you don't perform an unnecessary allocation for empty calls.header/src/main/java/org/zstack/header/vm/VmInstanceState.java (1)
56-57: 建议将Registering明确为过渡态并补充兜底迁移。当前
Registering只允许stopped -> Stopped,一旦收到其他事件会在状态机里直接抛异常;同时它未加入intermediateStates,容易被当作稳定离线态处理。建议补充至少一个异常/兜底迁移并纳入过渡态集合。可参考的最小改动
intermediateStates.add(VolumeMigrating); intermediateStates.add(VolumeRecovering); + intermediateStates.add(Registering); @@ Registering.transactions( - new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped) + new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped), + new Transaction(VmInstanceStateEvent.unknown, VmInstanceState.Unknown), + new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying) );Also applies to: 205-207
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java` around lines 56 - 57, Mark the enum/state Registering in VmInstanceState as an intermediate (transient) state by adding it to the intermediateStates collection and also include it in offlineStates if intended, and add a safe fallback transition from Registering (for example Registering -> Stopped or Registering -> Unknown/Error state) so unexpected events do not throw in the state machine; update the transition table/method that defines allowed transitions (where Registering is referenced) to include this fallback/exception path and ensure the intermediateStates set contains Registering so callers treat it as a transient state.header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java (1)
26-29:__example__建议补充示例数据,便于文档消费者理解返回格式。当前示例为空对象,自动生成文档时可读性较弱。
示例
public static APIGetVmInstanceMetadataFromPrimaryStorageEvent __example__() { APIGetVmInstanceMetadataFromPrimaryStorageEvent evt = new APIGetVmInstanceMetadataFromPrimaryStorageEvent(); + evt.setMetadata("{\"vmUuid\":\"d2f8...\",\"schemaVersion\":\"1.0\"}"); return evt; }🤖 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/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java` around lines 26 - 29, 在 APIGetVmInstanceMetadataFromPrimaryStorageEvent.__example__ 中不要返回空对象;构造并返回一个带有示例数据的事件实例:在方法中创建 APIGetVmInstanceMetadataFromPrimaryStorageEvent evt = new ...,为其填充典型字段(例如设置 primaryStorageUuid、vmInstanceUuid、以及一个示例元数据集合/映射,包含若干 key/value 如 "hostname","vmUuid","diskPath" 或类似的 metadataEntries),并设置事件为成功状态(例如 success 或对应的状态字段);最后返回填充好的 evt,以便自动生成文档时展示返回格式示例。header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java (1)
11-13: 避免通过 getter 暴露可变内部集合当前 getter 直接返回内部
List,调用方可绕过 setter 修改对象状态。建议返回副本或只读视图,和 setter 的防御性复制策略保持一致。建议修改
+import java.util.Collections; ... public List<VmMetadataScanEntry> getVmInstanceMetadata() { - return vmInstanceMetadata; + return Collections.unmodifiableList(vmInstanceMetadata); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java` around lines 11 - 13, getVmInstanceMetadata currently returns the internal mutable List directly, allowing callers to mutate object state; change ScanVmInstanceMetadataFromPrimaryStorageReply#getVmInstanceMetadata to return either a defensive copy (e.g., new ArrayList<>(vmInstanceMetadata)) or an unmodifiable view (e.g., Collections.unmodifiableList(vmInstanceMetadata)) to match the setter's defensive-copy behavior and prevent external modification of the internal collection.testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy (1)
525-543: 建议补充新元数据端点的行为级模拟,而不只是空响应这 5 个新 simulator 目前仅返回默认
Rsp,无法验证写入后读取、扫描结果、清理效果以及rebasedCount等关键行为。建议至少为WRITE/GET/SCAN/CLEANUP/NFS_PREFIX_REBASE_BACKING_FILES增加对应VFS.vfsHook,让测试能覆盖状态变化。🤖 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/NfsPrimaryStorageSpec.groovy` around lines 525 - 543, The five simulator stubs for NfsPrimaryStorageKVMBackend (WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH, CLEANUP_VM_METADATA_PATH, NFS_PREFIX_REBASE_BACKING_FILES_PATH) only return empty Rsp objects; update each simulator to exercise VFS.vfsHook-based behavior so tests can observe state changes: implement write logic in the WRITE handler to record metadata into a shared VFS-backed map, implement GET to read from that map and populate GetVmInstanceMetadataRsp, implement SCAN to return items from the map in ScanVmMetadataRsp, implement CLEANUP to remove entries and reflect that in CleanupVmMetadataRsp, and implement the NFS_PREFIX_REBASE_BACKING_FILES handler to update and return rebasedCount in PrefixRebaseBackingFilesRsp; use the existing NfsPrimaryStorageKVMBackendCommands response classes and keep modifications within the simulator blocks referencing those path constants.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (1)
1994-1998: 主机选择逻辑的一致性问题。当
msg.getHostUuid()不在已连接主机列表中时,代码会静默回退到第一个已连接主机。建议考虑在这种情况下记录警告日志,或者在hostUuid无效时返回错误,以帮助调用方发现配置问题。String hostUuid = msg.getHostUuid(); String finalHostUuid = hostUuid; if (hostUuid == null || !connectedHosts.stream().anyMatch(h -> h.getUuid().equals(finalHostUuid))) { + if (hostUuid != null) { + logger.warn(String.format("specified hostUuid[%s] is not connected, falling back to first connected host", hostUuid)); + } hostUuid = connectedHosts.get(0).getUuid(); }🤖 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 1994 - 1998, The host-selection silently falls back to connectedHosts.get(0) when msg.getHostUuid() is missing or not in connectedHosts; update the logic in NfsPrimaryStorage where hostUuid is computed (referencing msg.getHostUuid(), connectedHosts, and hostUuid/finalHostUuid) to either log a warning that includes the original requested hostUuid and the list of available connectedHosts before falling back, or return an explicit error/exception to the caller when the provided hostUuid is invalid; choose one behavior and implement it consistently (i.e., add a logger.warn(...) with context if you keep the fallback, or throw/return a clear error if you opt to fail fast).plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)
375-381: 建议把 prefix-rebase 请求也记录到 simulator config。这条 handler 是同组新增 endpoint 里唯一没有保存
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/LocalStorageSimulator.java` around lines 375 - 381, The prefixRebaseBackingFiles handler (method prefixRebaseBackingFiles) currently creates PrefixRebaseBackingFilesRsp but does not persist the incoming PrefixRebaseBackingFilesCmd into the simulator configuration, so tests cannot assert the inputs; modify prefixRebaseBackingFiles to store the deserialized PrefixRebaseBackingFilesCmd (or its relevant fields like filePaths and prefix parameters) into the simulator's config/state alongside other handlers (use the same config key/structure used by the other endpoints in LocalStorageSimulator), then continue to set rsp.rebasedCount and call reply(entity, rsp).sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java (1)
29-35:vmUuids/systemTags/userTags建议改为List<String>Line 29、Line 32、Line 35 的 raw List 与上一文件同样存在类型安全问题,建议保持一致改为显式泛型。
建议修改
- public java.util.List vmUuids; + public java.util.List<String> vmUuids; - public java.util.List systemTags; + public java.util.List<String> systemTags; - public java.util.List userTags; + public java.util.List<String> userTags;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java` around lines 29 - 35, The fields vmUuids, systemTags, and userTags in class CleanupVmInstanceMetadataAction are declared as raw java.util.List which is not type-safe; change their types to java.util.List<String> (and add an import or fully qualify if needed) so the declarations become List<String> vmUuids, List<String> systemTags, and List<String> userTags; update any usages, setters/getters, or annotations referencing these fields to expect List<String> accordingly to preserve compilation and type-safety.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3907-3912: 建议使用map()代替peek()进行数据转换。
peek()的设计意图是用于调试或日志记录,而非执行业务逻辑中的副作用操作。使用map()更加符合函数式编程的语义。♻️ 建议的改进
if (rsp.metadataEntries != null) { List<VmMetadataScanEntry> entries = rsp.metadataEntries.stream() .filter(Objects::nonNull) - .peek(entry -> entry.setHostUuid(hostUuid)) + .map(entry -> { + entry.setHostUuid(hostUuid); + return entry; + }) .collect(Collectors.toList()); reply.setVmInstanceMetadata(entries);🤖 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, Replace the side-effect use of Stream.peek in LocalStorageKvmBackend when transforming rsp.metadataEntries: instead of .peek(entry -> entry.setHostUuid(hostUuid)), use .map(entry -> { entry.setHostUuid(hostUuid); return entry; }) so the stream explicitly maps each VmMetadataScanEntry after setting hostUuid before collecting and calling reply.setVmInstanceMetadata; keep the null filter (Objects::nonNull) and collect as-is.compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java (1)
149-150: 空的@GlobalConfigValidation()注解无实际效果
VM_METADATA_LAST_REFRESH_VERSION的@GlobalConfigValidation()注解是空的,没有指定任何验证规则。此外缺少@GlobalConfigDef注解来定义默认值和类型。如果此配置是用于存储版本字符串,建议添加类型定义。如果不需要验证,可以移除空注解以避免混淆。
建议添加类型定义
- `@GlobalConfigValidation`() + `@GlobalConfigDef`(defaultValue = "", type = String.class, description = "last refresh version for vm metadata") + `@GlobalConfigValidation` public static GlobalConfig VM_METADATA_LAST_REFRESH_VERSION = new GlobalConfig(CATEGORY, "vm.metadata.lastRefreshVersion");请确认此配置的预期用途和默认值。
🤖 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/VmGlobalConfig.java` around lines 149 - 150, 注解 `@GlobalConfigValidation`() 在 VM_METADATA_LAST_REFRESH_VERSION 上是空的且无效;请确认该配置用于存储什么(例如版本字符串)并据此修复:要么为 VM_METADATA_LAST_REFRESH_VERSION 添加对应的 `@GlobalConfigDef`(類型=..., defaultValue="...") 明确定义类型和默认值,并在 `@GlobalConfigValidation`(...) 中指定合适的校验器或规则;要么如果不需要校验,则移除空的 `@GlobalConfigValidation` 注解以避免混淆;参考符号:VM_METADATA_LAST_REFRESH_VERSION、@GlobalConfigDef、@GlobalConfigValidation、GlobalConfig。
🤖 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 58-60: 问题:当 VmMetadataPathBuildExtensionPoint 扩展不存在时代码把 ext==null
视为“不需要 host”并继续将该主存储加入清理流程,导致对不支持元数据清理的主存储反复失败。修复:在
CleanupVmInstanceMetadataOnPrimaryStorageGC 中定位到使用
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
的位置(变量 ext),当 ext == null 时直接取消本次 GC(例如立即返回或跳过该主存储),不要将其当作 requireHost=false
处理;如果保留布尔变量 requireHost(调用 ext.requireHostForCleanup()),先做 ext != null 的判断,再根据
ext 决定是否继续,并在跳过时加入一条可追踪的日志以便排查。
In
`@header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy`:
- Around line 9-64: 为提升 API 文档可读性,给 VmMetadataScanEntry 文档中所有 field 的 desc
补上最小语义说明(例如 vmName: 虚拟机名称;vmCategory: 虚拟机类别;architecture: CPU 架构;schemaVersion:
元数据模式版本;metadataPath: 元数据在存储上的路径;hostUuid: 所属主机 UUID;sizeBytes:
元数据大小,单位为字节;lastUpdateTime: 最后更新时间,返回值为毫秒时间戳;incomplete: 是否为不完整的元数据,true
表示扫描到的不完整或损坏条目),在对应的字段定义(如 vmName、lastUpdateTime、incomplete 等)将空 desc
替换为这些简短说明即可。
In `@header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java`:
- Line 16: APIDeleteTagMsg's MetadataImpact annotation references a non-existent
Spring bean "SystemTagUuidToVmUuidResolver", causing a mismatch with
TagManagerImpl.handle which supports SystemTagVO, UserTagVO and TagPatternVO;
either implement the missing resolver class as a VmUuidFromApiResolver bean
named SystemTagUuidToVmUuidResolver, or remove/adjust the MetadataImpact
annotation on APIDeleteTagMsg to reflect the multiple tag types (or supply
appropriate resolvers for each tag type) so the annotation aligns with the
actual deletion logic in TagManagerImpl.handle.
In `@header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java`:
- Line 7: The MetadataImpact annotation on APIUpdateSystemTagMsg references a
non-existent bean "SystemTagUuidToVmUuidResolver", causing
NoSuchBeanDefinitionException; either remove or fix it: if you want to keep
metadata tracking implement and register a Spring bean named
SystemTagUuidToVmUuidResolver that implements the expected resolver interface
used by MetadataImpact and make its logic handle non-VM system tags (or delegate
based on resourceType), otherwise delete the `@MetadataImpact`(...) on class
APIUpdateSystemTagMsg so the VM-specific resolver is not applied to the generic
system-tag update API. Ensure the resolver bean name matches exactly and its
logic is generic or use different resolver names per API as appropriate.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java`:
- Around line 4-10: 为 VmMetadataPathBuildExtensionPoint 接口的每个方法补充符合仓库规范的
Javadoc:为 getPrimaryStorageType(), buildVmMetadataPath(String
primaryStorageUuid, String vmInstanceUuid), buildMetadataDir(String
primaryStorageUuid), validateMetadataPath(String primaryStorageUuid, String
path) 和默认方法 requireHostForCleanup() 添加简短描述、每个参数的 `@param` 说明、返回值的 `@return` 说明(包含可能的
null/空或错误语义)以及在必要时标注抛出的异常;对 requireHostForCleanup() 明确说明其默认返回值和何时应返回
true;保持方法签名不添加多余修饰符。
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java`:
- Around line 22-24: The setter setMetadataToCurrentPathMap currently allows
metadataToCurrentPathMap to be set to null which can cause NPEs downstream;
modify setMetadataToCurrentPathMap(Map<String,String> metadataToCurrentPathMap)
on class VmMetadataPathReplacementExtensionPoint to normalize null inputs by
assigning an empty map (e.g. Collections.emptyMap() or new HashMap<>()) instead
of null so callers that use the field need not null-check.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java`:
- Around line 7-15: Add Javadoc comments to the
VmMetadataResourcePersistExtensionPoint interface and each method: document
getPrimaryStorageType() return meaning; for
afterVolumePersist(primaryStorageUuid, resourceUuid, resourceType, hostUuid,
size, now) and afterSnapshotPersist(...) describe each parameter, when the
method is invoked (timing in the persist workflow), whether implementations must
be idempotent, and any exception/rollback expectations; for default
afterRegistrationRollback(List<String> resourceUuids) describe when rollbacks
are triggered and expected behavior for partial failures and idempotence; ensure
no extra modifiers are added to interface methods and keep descriptions concise
and consistent across implementations.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3548-3552: The log computes successful hosts incorrectly by
subtracting total failedHosts (which includes disconnectedHostUuids) from
connectedHostUuids.size(); change it to count only failures among connected
hosts: compute the number of failedConnectedHosts = failedHosts ∩
connectedHostUuids (or iterate connectedHostUuids and count those in
failedHosts) and use connectedHostUuids.size() - failedConnectedHosts as
successHosts in the logger.warn message for LocalStorageBase (use symbols
failedHosts, disconnectedHostUuids, connectedHostUuids, self.getUuid(),
logger.warn to locate and update the code).
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java`:
- Around line 10-11: 在 NfsPrimaryStorageBackend 中新增的 handle 方法引用了
GetVmInstanceMetadataFromPrimaryStorageMsg,但该类位于 org.zstack.header.vm
包且未被当前通配符导入覆盖;在文件顶部添加显式导入 import
org.zstack.header.vm.GetVmInstanceMetadataFromPrimaryStorageMsg; 来解决编译错误(保留现有
org.zstack.header.storage.primary.* 导入以覆盖其他消息类)。
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java`:
- Around line 149-150: 注解 `@GlobalConfigValidation`() 在
VM_METADATA_LAST_REFRESH_VERSION 上是空的且无效;请确认该配置用于存储什么(例如版本字符串)并据此修复:要么为
VM_METADATA_LAST_REFRESH_VERSION 添加对应的 `@GlobalConfigDef`(類型=...,
defaultValue="...") 明确定义类型和默认值,并在 `@GlobalConfigValidation`(...)
中指定合适的校验器或规则;要么如果不需要校验,则移除空的 `@GlobalConfigValidation`
注解以避免混淆;参考符号:VM_METADATA_LAST_REFRESH_VERSION、@GlobalConfigDef、@GlobalConfigValidation、GlobalConfig。
In
`@header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java`:
- Around line 6-13: The vmMetadata field and its accessors in
ReadVmInstanceMetadataReply are named inconsistently with other new reply DTOs
that use metadata; rename the private String vmMetadata, getVmMetadata(), and
setVmMetadata(...) to private String metadata, getMetadata(), and
setMetadata(...) in class ReadVmInstanceMetadataReply, update all internal
usages and any (de)serialization references or tests that refer to vmMetadata,
and run/adjust any DTO mapping code that expects the previous name to ensure
backward-compatible JSON/XML field names if needed (e.g., add
`@SerializedName/`@JsonProperty("vmMetadata") only if preserving external
contract).
In
`@header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java`:
- Around line 11-13: getVmInstanceMetadata currently returns the internal
mutable List directly, allowing callers to mutate object state; change
ScanVmInstanceMetadataFromPrimaryStorageReply#getVmInstanceMetadata to return
either a defensive copy (e.g., new ArrayList<>(vmInstanceMetadata)) or an
unmodifiable view (e.g., Collections.unmodifiableList(vmInstanceMetadata)) to
match the setter's defensive-copy behavior and prevent external modification of
the internal collection.
In
`@header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java`:
- Around line 26-29: 在
APIGetVmInstanceMetadataFromPrimaryStorageEvent.__example__
中不要返回空对象;构造并返回一个带有示例数据的事件实例:在方法中创建
APIGetVmInstanceMetadataFromPrimaryStorageEvent evt = new ...,为其填充典型字段(例如设置
primaryStorageUuid、vmInstanceUuid、以及一个示例元数据集合/映射,包含若干 key/value 如
"hostname","vmUuid","diskPath" 或类似的 metadataEntries),并设置事件为成功状态(例如 success
或对应的状态字段);最后返回填充好的 evt,以便自动生成文档时展示返回格式示例。
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java`:
- Around line 20-28: The public API VmMetadataDirtyService.markDirty(String
vmInstanceUuid, boolean storageStructureChange) should avoid a boolean flag
parameter; update the API to make intent explicit by either introducing an enum
ChangeType (e.g., ChangeType.STORAGE_STRUCTURE vs ChangeType.METADATA_ONLY) and
replacing the boolean with ChangeType, or by splitting into two descriptive
methods such as markDirty(String vmInstanceUuid) and
markDirtyWithStorageStructureChange(String vmInstanceUuid); update all
implementations and call sites that reference markDirty to use the new enum or
the two new methods and adjust javadoc/comments for the chosen approach.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 9-13: In VmUuidFromApiResolver.batchResolveVmUuids, avoid
allocating the result ArrayList before validating input; move the null/empty
check for fieldValues to the top and only create and return the result list
after confirming fieldValues is non-null and non-empty so you don't perform an
unnecessary allocation for empty calls.
In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java`:
- Around line 56-57: Mark the enum/state Registering in VmInstanceState as an
intermediate (transient) state by adding it to the intermediateStates collection
and also include it in offlineStates if intended, and add a safe fallback
transition from Registering (for example Registering -> Stopped or Registering
-> Unknown/Error state) so unexpected events do not throw in the state machine;
update the transition table/method that defines allowed transitions (where
Registering is referenced) to include this fallback/exception path and ensure
the intermediateStates set contains Registering so callers treat it as a
transient state.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3907-3912: Replace the side-effect use of Stream.peek in
LocalStorageKvmBackend when transforming rsp.metadataEntries: instead of
.peek(entry -> entry.setHostUuid(hostUuid)), use .map(entry -> {
entry.setHostUuid(hostUuid); return entry; }) so the stream explicitly maps each
VmMetadataScanEntry after setting hostUuid before collecting and calling
reply.setVmInstanceMetadata; keep the null filter (Objects::nonNull) and collect
as-is.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 375-381: The prefixRebaseBackingFiles handler (method
prefixRebaseBackingFiles) currently creates PrefixRebaseBackingFilesRsp but does
not persist the incoming PrefixRebaseBackingFilesCmd into the simulator
configuration, so tests cannot assert the inputs; modify
prefixRebaseBackingFiles to store the deserialized PrefixRebaseBackingFilesCmd
(or its relevant fields like filePaths and prefix parameters) into the
simulator's config/state alongside other handlers (use the same config
key/structure used by the other endpoints in LocalStorageSimulator), then
continue to set rsp.rebasedCount and call reply(entity, rsp).
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1994-1998: The host-selection silently falls back to
connectedHosts.get(0) when msg.getHostUuid() is missing or not in
connectedHosts; update the logic in NfsPrimaryStorage where hostUuid is computed
(referencing msg.getHostUuid(), connectedHosts, and hostUuid/finalHostUuid) to
either log a warning that includes the original requested hostUuid and the list
of available connectedHosts before falling back, or return an explicit
error/exception to the caller when the provided hostUuid is invalid; choose one
behavior and implement it consistently (i.e., add a logger.warn(...) with
context if you keep the fallback, or throw/return a clear error if you opt to
fail fast).
In `@sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java`:
- Around line 29-35: The fields vmUuids, systemTags, and userTags in class
CleanupVmInstanceMetadataAction are declared as raw java.util.List which is not
type-safe; change their types to java.util.List<String> (and add an import or
fully qualify if needed) so the declarations become List<String> vmUuids,
List<String> systemTags, and List<String> userTags; update any usages,
setters/getters, or annotations referencing these fields to expect List<String>
accordingly to preserve compilation and type-safety.
In `@testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy`:
- Around line 525-543: The five simulator stubs for NfsPrimaryStorageKVMBackend
(WRITE_VM_METADATA_PATH, GET_VM_INSTANCE_METADATA_PATH, SCAN_VM_METADATA_PATH,
CLEANUP_VM_METADATA_PATH, NFS_PREFIX_REBASE_BACKING_FILES_PATH) only return
empty Rsp objects; update each simulator to exercise VFS.vfsHook-based behavior
so tests can observe state changes: implement write logic in the WRITE handler
to record metadata into a shared VFS-backed map, implement GET to read from that
map and populate GetVmInstanceMetadataRsp, implement SCAN to return items from
the map in ScanVmMetadataRsp, implement CLEANUP to remove entries and reflect
that in CleanupVmMetadataRsp, and implement the NFS_PREFIX_REBASE_BACKING_FILES
handler to update and return rebasedCount in PrefixRebaseBackingFilesRsp; use
the existing NfsPrimaryStorageKVMBackendCommands response classes and keep
modifications within the simulator blocks referencing those path constants.
🪄 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: 2017040d-c019-420d-812a-21ecd675df0c
⛔ 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 (145)
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/zsv/V5.0.0__schema.sqlheader/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/APIScanVmInstanceMetadataFromPrimaryStorageReply.javaheader/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/primary/VmMetadataScanEntryDoc_zh_cn.groovyheader/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/APICleanupVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovyheader/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/APIGetVmInstanceMetadataFromPrimaryStorageEvent.javaheader/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/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/APIRegisterVmInstanceFromMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovyheader/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/APIUpdateVmInstanceMetadataEvent.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsg.javaheader/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovyheader/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/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/VmMetadataFlushStateVO.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO_.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
| VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); | ||
| boolean requireHost = ext != null && ext.requireHostForCleanup(); | ||
|
|
There was a problem hiding this comment.
缺少 metadata 扩展时应直接取消 GC。
这里把 ext == null 当成“无需 host”继续往下走,会把不支持元数据清理的主存储也送进 cleanup 流程。最终通常只会收到 operation not supported,GC 会进入无意义的周期性失败重试。
可参考的修正方式
VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class);
- boolean requireHost = ext != null && ext.requireHostForCleanup();
+ if (ext == null) {
+ logger.debug(String.format("[MetadataCleanupGC] ps[uuid:%s, type:%s] does not support vm metadata cleanup, cancel gc for vm[uuid:%s]",
+ primaryStorageUuid, psType, vmUuid));
+ completion.cancel();
+ return;
+ }
+ boolean requireHost = ext.requireHostForCleanup();🤖 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 58 - 60, 问题:当 VmMetadataPathBuildExtensionPoint 扩展不存在时代码把 ext==null
视为“不需要 host”并继续将该主存储加入清理流程,导致对不支持元数据清理的主存储反复失败。修复:在
CleanupVmInstanceMetadataOnPrimaryStorageGC 中定位到使用
pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class)
的位置(变量 ext),当 ext == null 时直接取消本次 GC(例如立即返回或跳过该主存储),不要将其当作 requireHost=false
处理;如果保留布尔变量 requireHost(调用 ext.requireHostForCleanup()),先做 ext != null 的判断,再根据
ext 决定是否继续,并在跳过时加入一条可追踪的日志以便排查。
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "vmName" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "vmCategory" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "architecture" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "schemaVersion" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "metadataPath" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "hostUuid" | ||
| desc "" | ||
| type "String" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "sizeBytes" | ||
| desc "" | ||
| type "Long" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "lastUpdateTime" | ||
| desc "" | ||
| type "Long" | ||
| since "5.0.0" | ||
| } | ||
| field { | ||
| name "incomplete" | ||
| desc "" | ||
| type "boolean" |
There was a problem hiding this comment.
文档字段 desc 全为空,建议补齐最小语义说明。
这会降低 API 文档可读性,尤其是 lastUpdateTime(单位)和 incomplete(判定含义)这类字段。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy`
around lines 9 - 64, 为提升 API 文档可读性,给 VmMetadataScanEntry 文档中所有 field 的 desc
补上最小语义说明(例如 vmName: 虚拟机名称;vmCategory: 虚拟机类别;architecture: CPU 架构;schemaVersion:
元数据模式版本;metadataPath: 元数据在存储上的路径;hostUuid: 所属主机 UUID;sizeBytes:
元数据大小,单位为字节;lastUpdateTime: 最后更新时间,返回值为毫秒时间戳;incomplete: 是否为不完整的元数据,true
表示扫描到的不完整或损坏条目),在对应的字段定义(如 vmName、lastUpdateTime、incomplete 等)将空 desc
替换为这些简短说明即可。
| 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
set -euo pipefail
echo "== 1) 查找 resolver 定义与注册 =="
rg -n --type=java -C3 'class\s+SystemTagUuidToVmUuidResolver\b|SystemTagUuidToVmUuidResolver|@Component\(|@Named\('
echo
echo "== 2) 查找 MetadataImpact 的解析入口(resolver 字段如何被消费) =="
rg -n --type=java -C3 'interface\s+MetadataImpact|class\s+MetadataImpact|resolver\s*=|getResolver|MetadataImpact.*resolver'
echo
echo "== 3) 查找 APIDeleteTagMsg 处理链,确认是否区分 System/User Tag =="
rg -n --type=java -C3 '\bAPIDeleteTagMsg\b|delete.*Tag|SystemTagVO|UserTagVO|TagVO'
echo
echo "== 4) 查找相关测试覆盖 =="
rg -n --type=java -C2 'APIDeleteTagMsg|SystemTagUuidToVmUuidResolver|MetadataImpact|delete.*tag' --iglob '*test*' --iglob '*Test*'Repository: MatheMatrix/zstack
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 1) 搜索 SystemTagUuidToVmUuidResolver 的定义与实现 =="
rg -n 'class\s+SystemTagUuidToVmUuidResolver|interface.*Resolver' --type=java -A 10 | head -60
echo
echo "== 2) 查找 MetadataImpact resolver 的消费入口(Spring bean 注册或运行时解析) =="
rg -n 'getResolver|resolver.*map|resolver.*get|@Bean.*Resolver' --type=java -B 2 -A 5 | head -80
echo
echo "== 3) 检查 APIDeleteTagMsg 的实现 - 它是否区分 SystemTagVO 和 UserTagVO =="
fd -e java | xargs grep -l 'APIDeleteTagMsg' | head -5
echo "---"
rg -n 'class APIDeleteTagMsg|implements.*Tag|SystemTagVO|UserTagVO' -A 20 header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java
echo
echo "== 4) 查找 DeleteTag 的处理器实现 =="
rg -n 'APIDeleteTagMsg|deleteTag|DeleteTagMsg' --type=java tag/src/main/java | head -30Repository: MatheMatrix/zstack
Length of output: 1462
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 直接搜索 SystemTagUuidToVmUuidResolver =="
rg 'SystemTagUuidToVmUuidResolver' --type=java -B 3 -A 15
echo
echo "== 搜索所有 *Resolver 类定义(特别是包含 Tag 的) =="
rg '^[^/]*class\s+\w*Tag\w*Resolver' --type=java -A 5Repository: MatheMatrix/zstack
Length of output: 3749
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索 Resolver 后缀的所有类,看看有哪些 resolver 被定义 =="
fd -e java -exec grep -l 'class.*Resolver.*implements\|class.*Resolver.*extends' {} \; | head -20
echo "---"
rg 'class.*Resolver' --type=java | grep -v 'Test\|test' | head -40
echo
echo "== 搜索 resolver 的注册或 map 初始化 =="
rg 'resolver.*=|resolverMap|resolver.*put|@Named.*Resolver|@Component.*Resolver' --type=java -B 2 -A 2 | head -60
echo
echo "== 查看 MetadataImpact 注解的具体定义与实现 =="
rg -n '@interface MetadataImpact|resolver|getResolver' header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java -A 20Repository: MatheMatrix/zstack
Length of output: 360
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索 Resolver 后缀的所有类 =="
rg 'class\s+\w*Resolver\s+(implements|extends)' --type=java | head -30
echo
echo "== 搜索 resolver 的注册、初始化或 map =="
rg 'put.*Resolver|resolver\s*=' --type=java | grep -v 'Test' | head -40
echo
echo "== 查看 MetadataImpact 注解的完整定义 =="
cat header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
echo
echo "== 搜索 MetadataImpact 在运行时的消费地点 =="
rg 'MetadataImpact|getResolver' --type=java | grep -E 'get|resolve|consume|process' | head -30Repository: MatheMatrix/zstack
Length of output: 86
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 直接查看 MetadataImpact.java 的定义 =="
cat header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
echo
echo "== 搜索所有包含 resolver 字符串的文件 =="
rg -l 'resolver' --type=java | head -20
echo
echo "== 搜索在运行时如何处理 resolver 字符串的代码 =="
rg -n 'resolver.*String|String.*resolver' --type=java -A 3 -B 3 | head -80Repository: MatheMatrix/zstack
Length of output: 3415
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索 `@Named` 或 `@Component` 中含 SystemTag 的 =="
rg '@Named|@Component' --type=java -A 5 | grep -B 5 -i 'systemtag\|tag.*resolver' | head -60
echo
echo "== 在 tag module 中搜索所有 Resolver 相关的类 =="
fd -e java tag/src/main/java | xargs grep -l 'Resolver\|resolver' | head -20
echo
echo "== 查看 tag module 的具体 resolver 实现 =="
rg 'class.*Resolver|interface.*Resolver' tag/src/main/java --type=java -A 10 | head -80Repository: MatheMatrix/zstack
Length of output: 105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 在 tag 模块查找所有包含 Resolver 的 Java 文件 =="
find tag -name '*.java' -exec grep -l 'Resolver' {} \; 2>/dev/null | head -20
echo
echo "== 查看 tag 模块的目录结构 =="
find tag -type f -name '*.java' | grep -E '(Manager|Impl|Service)' | head -20
echo
echo "== 搜索 TagManagerImpl 或类似的实现中是否有 resolver 注册 =="
rg -n 'class\s+TagManagerImpl|resolver' tag/src/main/java/org/zstack/tag/TagManagerImpl.java -A 5 -B 2 | head -80
echo
echo "== 搜索所有定义了 SystemTagUuidToVmUuidResolver 或类似名称的 bean =="
rg 'SystemTagUuidToVmUuidResolver|ResourceUuidToVmUuidResolver|VolumeUuidToVmUuidResolver' --type=java -B 5 -A 10 | head -100Repository: MatheMatrix/zstack
Length of output: 12946
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 查找所有实现 VmUuidFromApiResolver 接口的类 =="
rg 'implements VmUuidFromApiResolver|extends.*Resolver' --type=java -B 3 -A 15 | head -120
echo
echo "== 搜索 Spring 配置类或自动配置中的 resolver 注册 =="
rg '@Bean|@Component|@Named' --type=java | grep -i resolver | head -30
echo
echo "== 直接搜索 tag module 中的所有 Java 文件 =="
find tag/src/main/java -name '*.java' -type f | xargs grep -l 'VmUuidFromApiResolver\|Resolver'
echo
echo "== 查看完整的 TagManagerImpl 中是否有 bean 定义 =="
grep -n '@Bean\|@Component\|resolver' tag/src/main/java/org/zstack/tag/TagManagerImpl.java | head -20Repository: MatheMatrix/zstack
Length of output: 101
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索所有 `@Bean` 注解定义的 resolver =="
rg '@Bean\s' --type=java -A 5 | grep -B 2 -i 'resolver' | head -80
echo
echo "== 搜索 VmUuidFromApiResolver 接口的任何实现 =="
rg 'VmUuidFromApiResolver' --type=java -B 2 -A 10 | head -100
echo
echo "== 检查是否有其他形式的 resolver 注册(如 Spring XML 配置)=="
find . -name '*.xml' | xargs grep -l 'Resolver' 2>/dev/null | head -10
echo
echo "== 查看 APIDeleteTagMsg 处理器的实现 =="
rg 'class.*DeleteTag.*Processor|DeleteTag.*Handler' --type=java -A 10 | head -80Repository: MatheMatrix/zstack
Length of output: 93
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 查找 DeleteTag 消息的处理入口 =="
rg -n 'APIDeleteTagMsg' --type=java -B 2 -A 10 | grep -E 'class|handler|processor|handle|process' | head -40
echo
echo "== 查看 tag module 中处理 APIDeleteTagMsg 的代码 =="
rg 'APIDeleteTagMsg|deleteTag' tag/src/main/java --type=java -B 3 -A 10 | head -100
echo
echo "== 查看 header 中 APIDeleteTagMsg 的完整实现 =="
cat -n header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java | head -40
echo
echo "== 检查是否存在针对 UserTag 的删除 API =="
rg 'APIDeleteUserTag|UserTagMsg.*Delete' --type=java -B 2 -A 10Repository: MatheMatrix/zstack
Length of output: 11694
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 查看完整的 handle(APIDeleteTagMsg) 实现 =="
sed -n '726,800p' tag/src/main/java/org/zstack/tag/TagManagerImpl.java
echo
echo "== 查看 validate(APIDeleteTagMsg) 的完整实现 =="
rg -n 'private void validate\(APIDeleteTagMsg' tag/src/main/java/org/zstack/tag/TagApiInterceptor.java -A 40
echo
echo "== 搜索 SystemTagUuidToVmUuidResolver 在何处注册或定义 =="
rg -i 'systemtaguidtovmuuidresolver' --type=java -B 5 -A 10Repository: MatheMatrix/zstack
Length of output: 5189
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 全局搜索 resolver 的具体实现(包括 ResourceUuidToVmUuidResolver 等其他 resolver) =="
rg 'class.*UuidToVmUuidResolver|class.*UuidResolver' --type=java -B 2 -A 15 | head -150
echo
echo "== 搜索 resolver 被配置成 Bean 的地方 =="
rg '@Bean.*resolver|@Named.*resolver|new.*Resolver\(\)' --type=java -i -B 3 -A 5 | head -80
echo
echo "== 检查是否在类路径的其他位置有 resolver 相关实现 =="
find . -name '*Resolver*.java' -type f | head -30Repository: MatheMatrix/zstack
Length of output: 133
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索 MetadataImpact 的运行时处理 =="
rg 'getAnnotation.*MetadataImpact|MetadataImpact.*annotation' --type=java -B 3 -A 10 | head -100
echo
echo "== 搜索 resolver 字段的使用位置 =="
rg 'resolver\(\)|\.resolver' --type=java | grep -v Test | head -40
echo
echo "== 查找所有 VmUuidFromApiResolver 接口的实现 =="
rg 'class.*implements.*VmUuidFromApiResolver' --type=java -A 20 | head -100Repository: MatheMatrix/zstack
Length of output: 92
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 检查是否有任何 Resolver 类的实现存在 =="
find . -type f -name '*.java' | xargs grep -l 'VmUuidFromApiResolver' | head -20
echo
echo "== 查找所有包含 'resolver' 文本的 Java 文件(不分大小写) =="
rg 'resolver' --type=java -l | head -30
echo
echo "== 在这些文件中搜索实现类的定义 =="
rg 'resolver' --type=java -l | xargs grep 'class.*implements\|class.*extends' | grep -i resolver | head -30
echo
echo "== 搜索 '@Named' 或 '@Component' 中是否有 resolver 的注册 =="
rg '@Named|@Component' --type=java -A 2 | grep -B 2 -i 'resolver\|tag' | head -80Repository: MatheMatrix/zstack
Length of output: 246
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 查看 VmUuidFromApiResolver 接口的完整内容 =="
cat header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
echo
echo "== 搜索所有实现 VmUuidFromApiResolver 的类 =="
rg 'implements VmUuidFromApiResolver' --type=java -B 3 -A 20
echo
echo "== 搜索任何包含 'SystemTagUuid' 的类 =="
rg 'SystemTagUuid' --type=java -B 2 -A 10Repository: MatheMatrix/zstack
Length of output: 816
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== 搜索任何类中定义或创建 resolver 的位置,即使是动态的 =="
rg 'getBean.*resolver|bean.*resolver|resolver.*=.*new' --type=java -i -B 3 -A 5 | head -100
echo
echo "== 查找在注解处理时如何查找 resolver bean =="
rg 'BeanFactory|ApplicationContext|getBean' --type=java | grep -i resolver | head -20
echo
echo "== 检查是否有任何位置引用了字符串 'SystemTagUuidToVmUuidResolver' =="
grep -r 'SystemTagUuidToVmUuidResolver' . --include='*.java' --include='*.xml' --include='*.properties' 2>/dev/nullRepository: MatheMatrix/zstack
Length of output: 1000
resolver "SystemTagUuidToVmUuidResolver" 在代码库中不存在定义,需要检查 MetadataImpact 配置的正确性。
APIDeleteTagMsg 在第 16 行使用的 resolver = "SystemTagUuidToVmUuidResolver" 是一个 Spring bean ID,但在代码库中无法找到其实现类。VmUuidFromApiResolver 接口已定义,但没有任何实现类。
更严重的是,APIDeleteTagMsg 是一个通用删除 API,其实现(TagManagerImpl.handle)正确支持删除 SystemTagVO、UserTagVO 和 TagPatternVO 三种类型,而验证逻辑也正确处理了这三种情况。但 MetadataImpact 注解仅指定了 SystemTag 相关的 resolver,这导致配置与实际功能不匹配。
建议:
- 确认 SystemTagUuidToVmUuidResolver 是否应该被实现
- 如果 APIDeleteTagMsg 需要处理多种标签类型,resolver 配置可能需要调整或删除
- 如果 resolver 应该存在,需要添加其实现
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java` at line 16,
APIDeleteTagMsg's MetadataImpact annotation references a non-existent Spring
bean "SystemTagUuidToVmUuidResolver", causing a mismatch with
TagManagerImpl.handle which supports SystemTagVO, UserTagVO and TagPatternVO;
either implement the missing resolver class as a VmUuidFromApiResolver bean
named SystemTagUuidToVmUuidResolver, or remove/adjust the MetadataImpact
annotation on APIDeleteTagMsg to reflect the multiple tag types (or supply
appropriate resolvers for each tag type) so the annotation aligns with the
actual deletion logic in TagManagerImpl.handle.
| import org.zstack.header.message.APIMessage; | ||
| import org.zstack.header.message.APIParam; | ||
| import org.zstack.header.rest.RestRequest; | ||
| import org.zstack.header.vm.metadata.MetadataImpact; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
resolver_file="$(fd -i 'SystemTagUuidToVmUuidResolver.java' | head -n1)"
if [ -z "${resolver_file}" ]; then
echo "SystemTagUuidToVmUuidResolver.java not found"
exit 1
fi
echo "== Resolver file =="
echo "${resolver_file}"
echo
sed -n '1,260p' "${resolver_file}"
echo
echo "== Related callsites =="
rg -n -C3 --type=java '\bSystemTagUuidToVmUuidResolver\b|\bAPIUpdateSystemTagMsg\b'Repository: MatheMatrix/zstack
Length of output: 106
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1. 查看被评审的文件
echo "== APIUpdateSystemTagMsg.java content =="
cat -n "header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java" 2>/dev/null || echo "File not found at expected path"
echo
echo "== Search for SystemTagUuidToVmUuidResolver (case-insensitive) =="
fd -i 'systemtaguuidtovmuuidresolver' . 2>/dev/null || echo "Not found by fd"
echo
echo "== Search for resolver implementations =="
rg -l 'Resolver' --type=java header/src/main/java/org/zstack/header/tag/ 2>/dev/null | head -20
echo
echo "== Search for `@MetadataImpact` usage in system tag files =="
rg -n '@MetadataImpact' --type=java header/src/main/java/org/zstack/header/tag/ 2>/dev/null
echo
echo "== Search for SystemTag related resolver patterns =="
rg 'SystemTag.*Resolver|Resolver.*SystemTag' --type=java | head -20Repository: MatheMatrix/zstack
Length of output: 3285
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for SystemTagUuidToVmUuidResolver class definition (全仓库) =="
rg -n 'class\s+SystemTagUuidToVmUuidResolver' --type=java
echo
echo "== Search for resolver string references =="
rg -n '"SystemTagUuidToVmUuidResolver"' --type=java | head -20
echo
echo "== Search for Resolver interface/base class =="
rg -n 'interface.*Resolver|class.*Resolver.*\{' --type=java | grep -i 'metadata\|uuid' | head -20
echo
echo "== Check MetadataImpact annotation definition =="
fd -i 'metadataimpact.java' --type=fRepository: MatheMatrix/zstack
Length of output: 131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for MetadataImpact annotation definition =="
rg -n 'public.*@.*interface MetadataImpact|class MetadataImpact' --type=java -A5 | head -40
echo
echo "== Search for all resolver implementations in codebase =="
rg -n 'class.*Resolver' --type=java | grep -v 'test\|Test' | head -30
echo
echo "== Search for ResourceUuidToVmUuidResolver (the other resolver used) =="
rg -n 'class.*ResourceUuidToVmUuidResolver|implements.*Resolver' --type=java -A2 | head -40
echo
echo "== Check if resolver is registered via Spring or similar =="
rg -n '@Component|@Bean|@Service.*Resolver|SystemTagUuidToVmUuidResolver' --type=java | head -20Repository: MatheMatrix/zstack
Length of output: 1302
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for VmUuidFromApiResolver interface =="
rg -n 'interface\s+VmUuidFromApiResolver|class.*VmUuidFromApiResolver' --type=java -A5
echo
echo "== Search for all `@MetadataImpact` usages to see pattern =="
rg -n '@MetadataImpact' --type=java -B1 -A1
echo
echo "== Check MetadataImpact full definition =="
cat -n "header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java"
echo
echo "== Search for resolver bean registration patterns =="
rg -n '@Bean|@Component' --type=java | grep -i 'resolver\|metadata' | head -20Repository: MatheMatrix/zstack
Length of output: 29723
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for VmUuidFromApiResolver implementations =="
rg -n 'implements VmUuidFromApiResolver|class.*implements.*Resolver' --type=java | head -30
echo
echo "== Search for any `@Bean` or `@Component` resolver registrations =="
rg -B2 'VmUuidFromApiResolver|UuidResolver' --type=java | grep -A2 '@Bean\|@Component' | head -40
echo
echo "== List all Resolver implementations =="
rg -n 'class.*Resolver.*implements' --type=java | head -40
echo
echo "== Check system tag module for resolver =="
find . -path '*/tag*' -name '*Resolver*.java' -type f 2>/dev/null | head -20Repository: MatheMatrix/zstack
Length of output: 116
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check git status and current branch =="
git status --short | head -20
git log --oneline -5
echo
echo "== Final search: any file containing 'SystemTagUuidToVmUuidResolver' =="
rg 'SystemTagUuidToVmUuidResolver' --type=java --type=xml --type=properties
echo
echo "== Search in all files (not just Java) =="
find . -type f -name '*.java' -o -name '*.xml' -o -name '*.groovy' | xargs grep -l 'SystemTagUuidToVmUuidResolver' 2>/dev/null || echo "No files found"
echo
echo "== Check if resolver might be defined with different pattern =="
rg 'SystemTag.*Uuid.*Resolver|resolver.*=.*"SystemTag' --type=javaRepository: MatheMatrix/zstack
Length of output: 308
@MetadataImpact 注解引用的 resolver bean "SystemTagUuidToVmUuidResolver" 不存在于代码库中,会导致运行时异常。
Line 18 的 @MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SystemTagUuidToVmUuidResolver", field = "uuid") 引用了一个不存在的 Spring bean。当 Spring 容器初始化或首次处理该 API 时,会因为无法解析该 bean id 而抛出 NoSuchBeanDefinitionException。
另外,APIUpdateSystemTagMsg 是通用的 system tag 更新入口,不仅限于 VM 标签(resourceType 指向 SystemTagVO),不应绑定 VM 定向的 resolver。建议:
- 实现缺失的 resolver bean 或移除该注解
- 如果需要元数据追踪,应设计能处理非 VM 标签的 resolver,或为不同标签类型的 API 使用不同的 resolver
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java` at
line 7, The MetadataImpact annotation on APIUpdateSystemTagMsg references a
non-existent bean "SystemTagUuidToVmUuidResolver", causing
NoSuchBeanDefinitionException; either remove or fix it: if you want to keep
metadata tracking implement and register a Spring bean named
SystemTagUuidToVmUuidResolver that implements the expected resolver interface
used by MetadataImpact and make its logic handle non-VM system tags (or delegate
based on resourceType), otherwise delete the `@MetadataImpact`(...) on class
APIUpdateSystemTagMsg so the VM-specific resolver is not applied to the generic
system-tag update API. Ensure the resolver bean name matches exactly and its
logic is generic or use different resolver names per API as appropriate.
| String getPrimaryStorageType(); | ||
| String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid); | ||
| String buildMetadataDir(String primaryStorageUuid); | ||
| String validateMetadataPath(String primaryStorageUuid, String path); | ||
| default boolean requireHostForCleanup() { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
请为接口方法补充 Javadoc(仓库规范要求)。
当前接口方法缺少说明,会降低扩展实现方对参数语义和返回值约束的理解一致性。
建议补丁
public interface VmMetadataPathBuildExtensionPoint {
+ /**
+ * `@return` 当前扩展支持的主存储类型
+ */
String getPrimaryStorageType();
+
+ /**
+ * 构建某 VM 的 metadata 路径
+ */
String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid);
+
+ /**
+ * 构建 metadata 根目录
+ */
String buildMetadataDir(String primaryStorageUuid);
+
+ /**
+ * 校验并标准化 metadata 路径;非法时返回 null 或抛出业务异常(按实现约定)
+ */
String validateMetadataPath(String primaryStorageUuid, String path);
+
+ /**
+ * 清理 metadata 时是否要求携带 host 上下文
+ */
default boolean requireHostForCleanup() {
return false;
}
}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 4 - 10, 为 VmMetadataPathBuildExtensionPoint 接口的每个方法补充符合仓库规范的
Javadoc:为 getPrimaryStorageType(), buildVmMetadataPath(String
primaryStorageUuid, String vmInstanceUuid), buildMetadataDir(String
primaryStorageUuid), validateMetadataPath(String primaryStorageUuid, String
path) 和默认方法 requireHostForCleanup() 添加简短描述、每个参数的 `@param` 说明、返回值的 `@return` 说明(包含可能的
null/空或错误语义)以及在必要时标注抛出的异常;对 requireHostForCleanup() 明确说明其默认返回值和何时应返回
true;保持方法签名不添加多余修饰符。
| public void setMetadataToCurrentPathMap(Map<String, String> metadataToCurrentPathMap) { | ||
| this.metadataToCurrentPathMap = metadataToCurrentPathMap; | ||
| } |
There was a problem hiding this comment.
metadataToCurrentPathMap setter 建议做 null 兜底。
当前可被设置为 null,下游若直接使用返回 map 可能触发 NPE。建议与其他新代码保持一致,做空值归一化。
建议补丁
public void setMetadataToCurrentPathMap(Map<String, String> metadataToCurrentPathMap) {
- this.metadataToCurrentPathMap = metadataToCurrentPathMap;
+ this.metadataToCurrentPathMap = metadataToCurrentPathMap == null ? new HashMap<>() : metadataToCurrentPathMap;
}🤖 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 22 - 24, The setter setMetadataToCurrentPathMap currently allows
metadataToCurrentPathMap to be set to null which can cause NPEs downstream;
modify setMetadataToCurrentPathMap(Map<String,String> metadataToCurrentPathMap)
on class VmMetadataPathReplacementExtensionPoint to normalize null inputs by
assigning an empty map (e.g. Collections.emptyMap() or new HashMap<>()) instead
of null so callers that use the field need not null-check.
| 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); | ||
|
|
||
| default void afterRegistrationRollback(List<String> resourceUuids) {} |
There was a problem hiding this comment.
扩展点方法缺少 Javadoc,建议补齐。
该接口涉及持久化与回滚时序,建议为每个方法补充参数语义、调用时机与幂等要求,避免不同实现行为不一致。
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/VmMetadataResourcePersistExtensionPoint.java`
around lines 7 - 15, Add Javadoc comments to the
VmMetadataResourcePersistExtensionPoint interface and each method: document
getPrimaryStorageType() return meaning; for
afterVolumePersist(primaryStorageUuid, resourceUuid, resourceType, hostUuid,
size, now) and afterSnapshotPersist(...) describe each parameter, when the
method is invoked (timing in the persist workflow), whether implementations must
be idempotent, and any exception/rollback expectations; for default
afterRegistrationRollback(List<String> resourceUuids) describe when rollbacks
are triggered and expected behavior for partial failures and idempotence; ensure
no extra modifiers are added to interface methods and keep descriptions concise
and consistent across implementations.
| if (!failedHosts.isEmpty()) { | ||
| logger.warn(String.format("partial scan failure on local primary storage[uuid:%s], " + | ||
| "failedHosts=%s, successHosts=%d/%d", self.getUuid(), failedHosts, | ||
| connectedHostUuids.size() - failedHosts.size(), connectedHostUuids.size())); | ||
| } |
There was a problem hiding this comment.
日志中成功主机数计算有误
failedHosts 在 Line 3504 初始化时包含了 disconnectedHostUuids,后续迭代只在 connectedHostUuids 上进行。因此 connectedHostUuids.size() - failedHosts.size() 的计算可能产生负数。
例如:5 台主机中 2 台断开、3 台连接,其中 1 台连接主机扫描失败:
failedHosts.size()= 3(2 断开 + 1 失败)connectedHostUuids.size() - failedHosts.size()= 3 - 3 = 0
但实际上有 2 台连接主机成功了。
建议修复
} else {
reply.setVmInstanceMetadata(new ArrayList<>(allSummaries));
if (!failedHosts.isEmpty()) {
+ int connectedFailures = failedHosts.size() - disconnectedHostUuids.size();
logger.warn(String.format("partial scan failure on local primary storage[uuid:%s], " +
"failedHosts=%s, successHosts=%d/%d", self.getUuid(), failedHosts,
- connectedHostUuids.size() - failedHosts.size(), connectedHostUuids.size()));
+ connectedHostUuids.size() - connectedFailures, connectedHostUuids.size()));
}
}📝 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.
| if (!failedHosts.isEmpty()) { | |
| logger.warn(String.format("partial scan failure on local primary storage[uuid:%s], " + | |
| "failedHosts=%s, successHosts=%d/%d", self.getUuid(), failedHosts, | |
| connectedHostUuids.size() - failedHosts.size(), connectedHostUuids.size())); | |
| } | |
| if (!failedHosts.isEmpty()) { | |
| int connectedFailures = failedHosts.size() - disconnectedHostUuids.size(); | |
| logger.warn(String.format("partial scan failure on local primary storage[uuid:%s], " + | |
| "failedHosts=%s, successHosts=%d/%d", self.getUuid(), failedHosts, | |
| connectedHostUuids.size() - connectedFailures, connectedHostUuids.size())); | |
| } |
🤖 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 3548 - 3552, The log computes successful hosts incorrectly by
subtracting total failedHosts (which includes disconnectedHostUuids) from
connectedHostUuids.size(); change it to count only failures among connected
hosts: compute the number of failedConnectedHosts = failedHosts ∩
connectedHostUuids (or iterate connectedHostUuids and count those in
failedHosts) and use connectedHostUuids.size() - failedConnectedHosts as
successHosts in the logger.warn message for LocalStorageBase (use symbols
failedHosts, disconnectedHostUuids, connectedHostUuids, self.getUuid(),
logger.warn to locate and update the code).
| import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; | ||
| import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 验证新增消息类型的包位置
echo "=== Checking message class locations ==="
fd -t f -e java "GetVmInstanceMetadataFromPrimaryStorageMsg.java" --exec head -5 {}
fd -t f -e java "ScanVmInstanceMetadataFromPrimaryStorageMsg.java" --exec head -5 {}
fd -t f -e java "CleanupVmInstanceMetadataOnPrimaryStorageMsg.java" --exec head -5 {}
fd -t f -e java "RebaseVolumeBackingFileOnPrimaryStorageMsg.java" --exec head -5 {}Repository: MatheMatrix/zstack
Length of output: 1084
🏁 Script executed:
cd plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs
head -30 NfsPrimaryStorageBackend.java | cat -nRepository: MatheMatrix/zstack
Length of output: 1955
🏁 Script executed:
# 查找所有新增消息类型对应的 Reply 类包位置
fd -t f -e java "GetVmInstanceMetadataFromPrimaryStorageReply.java" --exec head -3 {}
fd -t f -e java "RebaseVolumeBackingFileOnPrimaryStorageReply.java" --exec head -3 {}
# 检查 NfsPrimaryStorageBackend 中新增的 handle 方法签名
grep -n "GetVmInstanceMetadataFromPrimaryStorageMsg\|ScanVmInstanceMetadataFromPrimaryStorageMsg\|CleanupVmInstanceMetadataOnPrimaryStorageMsg\|RebaseVolumeBackingFileOnPrimaryStorageMsg" plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.javaRepository: MatheMatrix/zstack
Length of output: 912
添加缺失的导入:GetVmInstanceMetadataFromPrimaryStorageMsg
新增的 handle 方法使用 GetVmInstanceMetadataFromPrimaryStorageMsg,但该消息类位于 org.zstack.header.vm 包中,不在第 8 行通配符导入 org.zstack.header.storage.primary.* 的覆盖范围内。需要添加显式导入:
import org.zstack.header.vm.GetVmInstanceMetadataFromPrimaryStorageMsg;其他消息类(ScanVmInstanceMetadataFromPrimaryStorageMsg、CleanupVmInstanceMetadataOnPrimaryStorageMsg、RebaseVolumeBackingFileOnPrimaryStorageMsg)及其对应的 Reply 类均位于 org.zstack.header.storage.primary 包中,已被第 8 行的通配符导入覆盖。
🤖 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 10 - 11, 在 NfsPrimaryStorageBackend 中新增的 handle 方法引用了
GetVmInstanceMetadataFromPrimaryStorageMsg,但该类位于 org.zstack.header.vm
包且未被当前通配符导入覆盖;在文件顶部添加显式导入 import
org.zstack.header.vm.GetVmInstanceMetadataFromPrimaryStorageMsg; 来解决编译错误(保留现有
org.zstack.header.storage.primary.* 导入以覆盖其他消息类)。
Resolves: ZSV-11559
Change-Id: I3cd9fa26b6af7bd47787ab88c5050683a3356059
sync from gitlab !9614