<feature>[vm-metadata]: data model, DTO and serialization#3551
<feature>[vm-metadata]: data model, DTO and serialization#3551ZStack-Robot wants to merge 1 commit intofeature-zsv-5.0.0-vm-registrationfrom
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新增完整的云主机元数据子系统:API、消息、JPA表、全局配置、存储后端扩展点与实现、SDK与测试模拟器,支持元数据读写、扫描、清理、注册、更新与脏标记机制,及主存储端代理交互。 Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant API as API 网关
participant VmMgr as VmInstanceManager
participant Bus as CloudBus
participant PS as PrimaryStorage 服务
participant Host as KVM Host/Agent
participant Storage as 后端存储
Client->>API: APIUpdateVmInstanceMetadataMsg (vmUuids...)
API->>VmMgr: handle(...)
alt vmMetadataDirtyMarker present
VmMgr->>VmMgr: markDirty(vmUuid...)
end
VmMgr->>Bus: UpdateVmInstanceMetadataMsg -> route by primaryStorageUuid
Bus->>PS: UpdateVmInstanceMetadataOnPrimaryStorageMsg
PS->>Host: POST /writeVmMetadata (WriteVmMetadataCmd)
Host->>Storage: 写入元数据文件
Storage-->>Host: success
Host-->>PS: WriteVmMetadataRsp
PS-->>Bus: UpdateVmInstanceMetadataOnPrimaryStorageReply
Bus-->>VmMgr: reply completes flow
sequenceDiagram
actor Client
participant API as API 网关
participant PS as PrimaryStorage 服务
participant Host as KVM Host/Agent
participant Storage as 后端存储
Client->>API: APIScanVmInstanceMetadataFromPrimaryStorageMsg (primaryStorageUuid)
API->>PS: ScanVmInstanceMetadataFromPrimaryStorageMsg
PS->>Host: POST /scanVmMetadata (ScanVmMetadataCmd)
Host->>Storage: 列出元数据文件
Storage-->>Host: VmMetadataScanEntry[]
Host-->>PS: ScanVmMetadataRsp
PS-->>API: ScanVmInstanceMetadataFromPrimaryStorageReply
API->>Client: 返回扫描结果
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (12)
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java-3-15 (1)
3-15:⚠️ Potential issue | 🟡 Minor修复注释乱码与
@see标签格式Line 4、Line 6、Line 10 等处出现
�?乱码;Line 14 的@see链接闭合格式也已损坏(/a>)。这会影响可读性,并可能导致 Javadoc 生成告警。🛠️ 建议修复
/** - * SharedBlock 元数据存储的容量常量�?Payload 大小保护阈值�? + * SharedBlock 元数据存储的容量常量与 Payload 大小保护阈值。 * - * <p>SharedBlock(sblk)使用固定大小的 LV 存储 VM 元数据,采用�?Slot 布局�? + * <p>SharedBlock(sblk)使用固定大小的 LV 存储 VM 元数据,采用双 Slot 布局: * <pre> * [ LV Header (4096B) ][ Slot-A ][ Slot-B ] * Slot 大小 = (lvSize - headerSize) / 2,向下对齐到 4096 - * Slot Header = 36B(Magic 4B + SeqNum 8B + SlotOffset 8B + SlotCapacity 8B + PayloadLen 8B�? + * Slot Header = 36B(Magic 4B + SeqNum 8B + SlotOffset 8B + SlotCapacity 8B + PayloadLen 8B) * 可用 Payload = SlotCapacity - SlotHeaderSize * </pre> * - * `@see` <a href="vm-metadata-02b §10.0">Part 02b §10.0 容量公式与常�?/a> + * `@see` <a href="vm-metadata-02b">Part 02b §10.0 容量公式与常量</a> */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/VmMetadataConstants.java` around lines 3 - 15, The class-level Javadoc in VmMetadataConstants.java contains garbled characters and a broken `@see` tag; open the Javadoc for the VmMetadataConstants class and replace the corrupted sequences (e.g. "�?" occurrences on line 4/6/10) with the intended text or proper punctuation (fix Chinese characters/punctuation or convert to plain English), correct the malformed `@see` closing tag from "/a>" to a proper </a> tag and ensure the URL/anchor text is valid, and save the file with UTF-8 encoding so the comment renders correctly during Javadoc generation.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java-5-17 (1)
5-17:⚠️ Potential issue | 🟡 Minor问题:Javadoc 注释存在字符编码问题(乱码)。
注释中多处出现乱码字符,例如:
- 第 6 行:
定义�?- 第 8 行:
消费�?- 第 13 行:
路径�?这会影响文档的可读性。请检查源文件的编码格式(应为 UTF-8)并修复这些注释。
📝 建议修复注释
/** - * 虚拟机元数据相关 CanonicalEvent 定义�? + * 虚拟机元数据相关 CanonicalEvent 定义。 * - * <p>通过 {`@code` EventFacade.fire()} 发布,供监控系统和巡检机制消费�?/p> + * <p>通过 {`@code` EventFacade.fire()} 发布,供监控系统和巡检机制消费。</p> */ public class VmMetadataCanonicalEvents { /** - * GC 放弃后的 stale 事件路径�? + * GC 放弃后的 stale 事件路径。 * - * <p>�?{`@code` UpdateVmInstanceMetadataGC} 超过最大重试次数后发布此事件, - * {`@code` MetadataHealthCheckJob} 监听此事件将 VM 加入优先刷新队列�?/p> + * <p>当 {`@code` UpdateVmInstanceMetadataGC} 超过最大重试次数后发布此事件, + * {`@code` MetadataHealthCheckJob} 监听此事件将 VM 加入优先刷新队列。</p> */ public static final String VM_METADATA_STALE_PATH = "/vm/metadata/stale";🤖 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/VmMetadataCanonicalEvents.java` around lines 5 - 17, The Javadoc in VmMetadataCanonicalEvents contains garbled characters due to encoding; open the source file for VmMetadataCanonicalEvents, ensure the file is saved in UTF-8 encoding, and correct the corrupted Javadoc text (fix lines mentioning “定义�?”, “消费�?”, “路径�?” etc.) so the comments read properly (mentioning EventFacade.fire(), UpdateVmInstanceMetadataGC and MetadataHealthCheckJob where relevant); re-run a build or IDE reindex to confirm the Javadoc renders correctly.header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy-15-22 (1)
15-22:⚠️ Potential issue | 🟡 Minor路径引用的类名不一致。
第 17 行的路径
org.zstack.header.vm.metadata.APIUpdateVmMetadataEvent.error应改为org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error。实际的 Event 类名为APIUpdateVmInstanceMetadataEvent(包含 "Instance"),与当前路径中的类名不符。修复路径引用
ref { name "error" - path "org.zstack.header.vm.metadata.APIUpdateVmMetadataEvent.error" + path "org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error" desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" type "ErrorCode" since "5.0.0" clz ErrorCode.class }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy` around lines 15 - 22, The ref path in the metadata doc incorrectly references class org.zstack.header.vm.metadata.APIUpdateVmMetadataEvent.error; update that path to org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error to match the real event class name (APIUpdateVmInstanceMetadataEvent) and keep the ref name "error" and clz ErrorCode.class unchanged so the reference points to the correct event class.header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java-3-49 (1)
3-49:⚠️ Potential issue | 🟡 MinorJavaDoc 存在乱码与标签损坏,建议立即修复。
Line 3 到 Line 49 的注释里多处出现乱码(
�?)和损坏的 HTML 标签(如�?/p>),会影响 API 语义理解和后续维护。建议统一文件编码并修正注释文本。建议修复(示例)
- * 虚拟机元数据注册参数�? + * 虚拟机元数据注册参数 ... - * <p>封装从元数据注册虚拟机时需要的新环境上下文信息�?/p> + * <p>封装从元数据注册虚拟机时需要的新环境上下文信息。</p>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/VmInstanceMetadataRegistrationSpec.java` around lines 3 - 49, The class VmInstanceMetadataRegistrationSpec contains corrupted JavaDoc (garbled characters like �? and broken HTML tags like �?/p>); fix by converting the file to UTF-8 if needed and correcting the JavaDoc blocks for the class and the field comments (zoneUuid, primaryStorageUuid, accountUuid, oldPathIdentifier, newPathIdentifier) to valid text and well-formed HTML/JavaDoc tags (use /** ... */ with <p>...</p> or plain paragraphs and proper list tags), ensuring Chinese descriptions are readable and tags like </p>, <ul>, <li> are properly closed and escaped where required.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java-4-11 (1)
4-11:⚠️ Potential issue | 🟡 Minor请修复 Javadoc 乱码与标签闭合错误
Line 4 到 Line 11 出现乱码(
�?)和错误闭合标签(如�?/p>、�?/li>),会影响可读性和文档生成质量。📝 建议修改
/** - * 虚拟机元数据分类�? + * 虚拟机元数据分类 * - * <p>用于区分元数据所属的 VM 类型,注册恢复时按不同分类执行不同的恢复逻辑�?/p> + * <p>用于区分元数据所属的 VM 类型,注册恢复时按不同分类执行不同的恢复逻辑</p> * * <ul> - * <li>{`@link` `#REGULAR`} �?普通云主机</li> - * <li>{`@link` `#TEMPLATE`} �?模板虚拟�?/li> - * <li>{`@link` `#TEMPLATE_CACHE`} �?模板虚拟机缓�?/li> + * <li>{`@link` `#REGULAR`}:普通云主机</li> + * <li>{`@link` `#TEMPLATE`}:模板虚拟机</li> + * <li>{`@link` `#TEMPLATE_CACHE`}:模板虚拟机缓存</li> * </ul> */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/VmMetadataCategory.java` around lines 4 - 11, 修复 VmMetadataCategory 类的 Javadoc 中的乱码与错误闭合标签:在 VmMetadataCategory 顶部注释段落中将所有出现的“�?”替换为正确中文字符并修正 HTML 标签(例如把 “�?/p>” 改为 "</p>" 或直接使用空行分段,把 “�?/li>” 改为 "</li>" 或关闭相应的 <li>),确保枚举项说明对齐(REGULAR,TEMPLATE,TEMPLATE_CACHE)且标签成对匹配,或者简化为纯文本列出三项以避免 HTML 转义问题;保存后运行 javadoc 或 IDE 注释预览确认无乱码与未闭合标签。header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java-18-19 (1)
18-19:⚠️ Potential issue | 🟡 Minor保持列表字段非空,避免调用方空指针。
Line 18-19 建议做
null归一化处理,和 Line 12 的默认值语义保持一致。建议修复
public void setVmInstanceMetadata(List<VmInstanceMetadataSummary> vmInstanceMetadata) { - this.vmInstanceMetadata = vmInstanceMetadata; + this.vmInstanceMetadata = vmInstanceMetadata == null ? new ArrayList<>() : 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/APIScanVmInstanceMetadataFromPrimaryStorageReply.java` around lines 18 - 19, In APIScanVmInstanceMetadataFromPrimaryStorageReply update the setter setVmInstanceMetadata to normalize null inputs: if the passed vmInstanceMetadata is null, assign an empty list to the vmInstanceMetadata field (to match the class's default non-null semantics), otherwise assign the provided list; reference the setVmInstanceMetadata method and the vmInstanceMetadata field when making the change.header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy-16-18 (1)
16-18:⚠️ Potential issue | 🟡 Minor补充
success字段描述,避免文档歧义。Line 17 的
desc为空,建议写明成功/失败语义。建议修复
field { name "success" - desc "" + desc "操作是否成功,true 表示成功,false 表示失败" type "boolean" since "5.0.0" }🤖 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/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy` around lines 16 - 18, 在 APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy 中为字段名为 "success" 的描述补充明确语义(位于 name "success" / desc "" / type "boolean" 的条目),将 desc 填写为简短说明例如“操作是否成功;true 表示成功,false 表示失败并可能伴随 error 信息”,以消除歧义并与其它文档字段语义保持一致。header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java-15-16 (1)
15-16:⚠️ Potential issue | 🟡 Minor避免在 Setter 中接受
null破坏默认非空约束。Line 15-16 直接赋值可能把
vmInstanceMetadata置为null,下游遍历时会有空指针风险。建议修复
public void setVmInstanceMetadata(List<VmInstanceMetadataSummary> vmInstanceMetadata) { - this.vmInstanceMetadata = vmInstanceMetadata; + this.vmInstanceMetadata = vmInstanceMetadata == null ? new ArrayList<>() : 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 15 - 16, The setter setVmInstanceMetadata currently assigns the passed List directly and can set the field vmInstanceMetadata to null; change it to guard against null by assigning an empty List when the argument is null (e.g., use Collections.emptyList() or new ArrayList<>()) so vmInstanceMetadata is never null and downstream iteration won't NPE. Ensure you update setVmInstanceMetadata in class ScanVmInstanceMetadataFromPrimaryStorageReply to perform this null-check/replace.header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy-20-20 (1)
20-20:⚠️ Potential issue | 🟡 Minor补充请求描述,提升 API 文档可读性。
Line 20 的
desc为空,建议填充简短说明。✏️ 建议修改
- desc """""" + desc """扫描指定主存储上的云主机元数据摘要"""🤖 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/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy` at line 20, 在 APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中将空的 desc """""" 填写为一段简短的中文描述,说明该 API 的用途(例如:“扫描指定主存储上的虚拟机实例元数据,返回发现的元数据条目以供同步/校验”),确保在文件中定位到 desc 字段并替换为空字符串的地方(符号:desc 在 APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy)即可。header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy-29-31 (1)
29-31:⚠️ Potential issue | 🟡 Minor补全
success字段描述,避免文档语义缺失。Line 30 的
desc为空,建议补充明确语义。✏️ 建议修改
field { name "success" - desc "" + desc "是否清理成功" type "boolean" since "5.0.0" }🤖 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/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy` around lines 29 - 31, The event documentation for APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy leaves the "success" field description empty; update the desc for the "success" property (the entry with name "success") to a concise Chinese description such as indicating whether the cleanup operation completed successfully (true) or failed (false), making sure the wording matches existing doc style and clarifies the meaning and expected boolean values.header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java-18-19 (1)
18-19:⚠️ Potential issue | 🟡 Minor为
vmInstanceUuid补上 VM 资源校验。当前这里只是裸
@APIParam,错误的 UUID 会一直下沉到主存储处理阶段才暴露。这里直接声明resourceType = VmInstanceVO.class,可以把无效请求拦在 API 层。💡 建议修改
import org.zstack.header.message.APIParam; import org.zstack.header.message.APISyncCallMessage; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.VmInstanceVO; @@ - `@APIParam` + `@APIParam`(resourceType = VmInstanceVO.class) private String vmInstanceUuid;🤖 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/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java` around lines 18 - 19, 在 APIGetVmInstanceMetadataFromPrimaryStorageMsg 类中,vmInstanceUuid 字段当前只有裸 `@APIParam`,需在注解上补上资源校验以在 API 层拦截无效 VM UUID;修改 vmInstanceUuid 的 `@APIParam` 为包含 resourceType = VmInstanceVO.class 的形式,确保请求在进入主存储处理前校验 VM 资源存在性。header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java-17-17 (1)
17-17:⚠️ Potential issue | 🟡 Minor
MAX_WRITE_SEQUENCE的字面量值与命名语义不匹配。在 Java 中,
0xFFFFFFFFFFFFFFFFL作为有符号long的实际值是-1L(二进制补码表示)。常量名称MAX_WRITE_SEQUENCE暗示这是一个上界值,但实际值为-1会产生误导。如果这是用作哨兵值或回绕标记,应重命名为
WRITE_SEQUENCE_WRAP_AROUND或类似名称以清晰表达意图;如果确实应表示最大写序号,则应使用Long.MAX_VALUE或其他正值。🤖 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` at line 17, The constant VmInstanceMetadataConstants.MAX_WRITE_SEQUENCE currently uses the literal 0xFFFFFFFFFFFFFFFFL which is -1L as a signed long and therefore misleading; either (A) if this is intended as a sentinel/wrap-around marker, rename the constant to WRITE_SEQUENCE_WRAP_AROUND (or WRITE_SEQUENCE_SENTINEL) and update all usages to reflect the sentinel semantics and adjust Javadoc, or (B) if it should represent the maximum write sequence numeric bound, change the value to Long.MAX_VALUE (or an appropriate positive upper bound) and update Javadoc and any logic that depends on it; make sure to update all references to the symbol (MAX_WRITE_SEQUENCE) in the codebase to the new name or handle the new value appropriately.
🧹 Nitpick comments (9)
header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java (1)
7-11: 建议:为接口方法添加 Javadoc 注释。根据编码规范,接口方法必须配有有效的 Javadoc 注释。建议添加文档说明每个方法的用途。
📝 建议添加 Javadoc
public interface VmUuidFromApiResolver { + /** + * 判断此解析器是否支持处理给定的 API 消息。 + * + * `@param` msg 待检查的 API 消息 + * `@return` 如果支持则返回 true + */ boolean supports(APIMessage msg); + /** + * 从 API 消息中解析出相关的 VM UUID 列表。 + * + * `@param` msg API 消息 + * `@return` VM UUID 列表,如果无相关 VM 则返回空列表 + */ List<String> resolveVmUuids(APIMessage 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/VmUuidFromApiResolver.java` around lines 7 - 11, Add Javadoc comments to the VmUuidFromApiResolver interface: document the interface purpose and add Javadoc for the supports(APIMessage msg) method describing what messages it accepts and what the boolean result means, and for resolveVmUuids(APIMessage msg) describing that it extracts VM UUID(s) from the given APIMessage and explain the return value (e.g., list of UUID strings, empty list if none). Include `@param` and `@return` tags for both methods to satisfy the coding standard.header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java (1)
3-8: 建议:考虑使用更具描述性的字段名和封装方式。
- 字段
vo命名过于简短,建议使用更明确的名称如voData或voSnapshot,以提高可读性。- 作为 DTO 类,可以考虑添加 getter/setter 方法以保持与项目其他部分的一致性。
♻️ 建议的改进
public class ResourceMetadata { public String resourceUuid; - public String vo; + public String voSnapshot; public String systemTags; public String resourceConfigs; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java` around lines 3 - 8, The DTO ResourceMetadata exposes a cryptic field name `vo` and uses public fields; rename `vo` to a more descriptive identifier (e.g., `voData` or `voSnapshot`) and convert the public fields (resourceUuid, vo/voData, systemTags, resourceConfigs) to private with standard getter/setter methods on the ResourceMetadata class to match project conventions; update all references/usages of ResourceMetadata accordingly (constructor, serializers, callers) to use the new field name and accessors to avoid breaking behavior.header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java (1)
40-53: 建议在 Interceptor 启动时校验 resolver 属性的非空性根据 Javadoc,当
value为CONFIG或STORAGE时,resolver属性是必需的。然而,当前设计允许resolver的默认值为空字符串,这可能导致开发者在忘记指定 resolver 时产生隐藏的配置错误。建议在 Interceptor 启动时增加校验逻辑,当检测到
@MetadataImpact注解且value != NONE但resolver为空或不存在对应的 Spring bean 时,抛出明确的配置异常,以便在应用启动阶段快速发现问题。🤖 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/MetadataImpact.java` around lines 40 - 53, Add startup validation in the Interceptor that scans for usages of the MetadataImpact annotation and enforces that when MetadataImpact.value() != Impact.NONE the resolver() is non-empty and resolves to an existing Spring bean (VmUuidFromApiResolver) via ComponentLoader.getComponentByBeanName(); if the resolver is blank or the bean lookup fails, throw a clear configuration/runtime exception to fail fast during application startup so misconfigured annotations are detected early. Ensure the check references MetadataImpact.resolver(), MetadataImpact.value(), Impact.NONE, VmUuidFromApiResolver and uses ComponentLoader.getComponentByBeanName() during the Interceptor initialization phase.header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java (1)
10-13: 建议将计数字段改为原始类型并初始化列表字段为空集合Line 10 和 Line 11 使用
Integer包装类型会引入可空语义,建议改为long原始类型,以避免装箱拆箱和潜在 NPE 风险;Line 12 的failedVmUuids也应默认初始化为空集合。♻️ 建议修改
package org.zstack.header.vm.metadata; import org.zstack.header.message.APIEvent; import org.zstack.header.rest.RestResponse; +import java.util.Collections; import java.util.List; `@RestResponse`(fieldsTo = {"all"}) public class APICleanupVmInstanceMetadataEvent extends APIEvent { - private Integer totalCleaned; - private Integer totalFailed; - private List<String> failedVmUuids; + private long totalCleaned; + private long totalFailed; + private List<String> failedVmUuids = Collections.emptyList(); @@ - public Integer getTotalCleaned() { + public long getTotalCleaned() { return totalCleaned; } - public void setTotalCleaned(Integer totalCleaned) { + public void setTotalCleaned(long totalCleaned) { this.totalCleaned = totalCleaned; } @@ - public Integer getTotalFailed() { + public long getTotalFailed() { return totalFailed; } - public void setTotalFailed(Integer totalFailed) { + public void setTotalFailed(long totalFailed) { this.totalFailed = totalFailed; }🤖 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 - 13, 在 APICleanupVmInstanceMetadataEvent 类中将包装类型字段 totalCleaned 和 totalFailed 改为原始类型 long(避免装箱/拆箱开销与可空语义),并在字段声明处将 failedVmUuids 默认初始化为一个空集合(例如 Collections.emptyList() 或 new ArrayList<>());同时检查并调整任何 getter/setter 或序列化逻辑以匹配类型更改(totalCleaned、totalFailed、failedVmUuids)。compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java (1)
208-213: 注释语义与实际允许消息不完全一致。Line 208-209 提到允许“metadata-related reads”,但当前白名单未体现 read 类消息。建议同步注释或补齐消息列表,避免后续误判状态能力边界。
As per coding guidelines, “对于较长的注释,需要仔细校对并随代码更新,确保内容正确。”
🤖 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/AbstractVmInstance.java` around lines 208 - 213, The comment about allowed "metadata-related reads" is out of sync with the whitelist in allowedOperations.addState for VmInstanceState.Registering (currently only ChangeVmMetaDataMsg, APIDestroyVmInstanceMsg, DestroyVmInstanceMsg); either update the comment to remove the "metadata-related reads" phrase to reflect the actual allowed messages, or add the appropriate read message class names to the addState call (e.g., the project's VM metadata read/query message classes) so the whitelist matches the comment, ensuring you update the same call to allowedOperations.addState and keep VmInstanceState.Registering, ChangeVmMetaDataMsg, APIDestroyVmInstanceMsg and DestroyVmInstanceMsg consistent.header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy (1)
20-20: 建议补充请求体说明描述。Line 20 的
desc为空,建议填写简短说明,提升 API 文档自解释性。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/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy` at line 20, The API documentation file APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy has an empty desc string for the message doc; update the desc to a concise Chinese description of the request body and purpose (e.g., explain what parameters are expected and what the API returns) so the doc is self-descriptive—locate the desc """""" entry in APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy and replace the empty string with a short explanatory sentence in Chinese that summarizes the request payload and intent.header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy (1)
19-23: 建议补充success字段说明文本。当前
desc为空,API 文档可读性较弱;建议写明成功标记语义(如“操作是否成功”)。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/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy` around lines 19 - 23, 在 APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy 中为字段名 "success" 补充描述,目前 desc 为空;将 desc 填为简短清晰的中文说明(例如 “操作是否成功,true 表示成功,false 表示失败”)以提高文档可读性,确保在 APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn 类/文件中更新该 success 字段的 desc 属性并保持风格与其它字段一致。plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java (1)
357-374: 建议让get/scansimulator 支持可配置的非空返回。这两个 endpoint 现在始终回空列表,而对应的 agent 响应类型本身带有
entries/metadataEntries字段(plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.javaaround Lines 79-120)。这样一来,新增 DTO/序列化链路在测试里只能覆盖“空结果”分支,字段映射问题不容易暴露。可以参考readVmMetadataContent的做法,把返回 entries 放到LocalStorageSimulatorConfig里可配置化。🤖 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 357 - 374, Modify the getVmInstanceMetadata and scanVmMetadata simulator handlers to return configurable non-empty results instead of always empty lists: read pre-populated entries from LocalStorageSimulatorConfig (similar to readVmMetadataContent) and set them on GetVmInstanceMetadataRsp.entries and ScanVmMetadataRsp.metadataEntries respectively before calling reply(entity, rsp); also add/use collection fields in LocalStorageSimulatorConfig to allow tests to inject DTOs for both endpoints so serialization/mapping can be exercised.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java (1)
96-106: 给新增接口契约补上 Javadoc。这几组
handle(...)现在只有签名,没有说明hostUuid的选择前提、reply 的关键字段以及失败语义,后续实现方很难对齐行为。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 - 106, For each of the six handle(...) declarations (UpdateVmInstanceMetadataOnPrimaryStorageMsg, ReadVmInstanceMetadataOnPrimaryStorageMsg, GetVmInstanceMetadataFromPrimaryStorageMsg, ScanVmInstanceMetadataFromPrimaryStorageMsg, CleanupVmInstanceMetadataOnPrimaryStorageMsg, RebaseVolumeBackingFileOnPrimaryStorageMsg) add a Javadoc block that states (1) when and how hostUuid should be chosen (caller vs candidate host vs null), (2) which fields in the corresponding Reply type are mandatory for success and which convey important metadata/errors, and (3) the failure semantics (what errors mean, whether implementations should retry or return immediate failure and how to populate the reply on error). Keep method signatures unchanged and ensure the Javadoc follows project style and contains examples or expected return behavior where helpful.
🤖 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/VmGlobalConfig.java`:
- Around line 140-141: VM metadata global config key is inconsistent:
VmGlobalConfig declares VM_METADATA with key "vm.metadata" but the canonical
constant VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED is
"vm.metadata.enabled"; make them identical. Update the GlobalConfig
instantiation in VmGlobalConfig (VM_METADATA) to use the same key as
VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED (or update the
constant and all call sites if you intend "vm.metadata" to be canonical) so
reads/writes reference the same global config key.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java`:
- Around line 2928-2935: The handler method
handle(APIUpdateVmInstanceMetadataMsg msg) currently always publishes a success
APIUpdateVmInstanceMetadataEvent even when the optional vmMetadataDirtyMarker is
null; change it so that if vmMetadataDirtyMarker == null you do not publish a
success event but instead set the event to an error (or publish a failed event)
indicating the metadata-dirty capability is unavailable. Concretely: inside
handle(APIUpdateVmInstanceMetadataMsg) check vmMetadataDirtyMarker for null
before looping over msg.getVmUuids(); if null, populate the
APIUpdateVmInstanceMetadataEvent with an error/failure code and descriptive
message about the missing VmMetadataDirtyService and then publish that error
event; otherwise perform vmMetadataDirtyMarker.markDirty(vmUuid, true) for each
vmUuid and publish the success event as before. Ensure you reference
vmMetadataDirtyMarker, vmUuid, vmMetadataDirtyMarker.markDirty(...), and
bus.publish(event) when making the change.
- Around line 2878-2923: The code in VmInstanceManagerImpl silently skips VMs
with psUuid == null so they aren't counted in evt totals; instead, when
iterating vmUuids (the loop that builds msgs and checks psUuid), mark those with
null primary storage as failures by incrementing a failure counter and adding
the vmUuid to the failedVmUuids collection that will be published via evt (or
accumulate them and merge with failures collected from the
CloudBusListCallBack), then proceed to build/send msgs for the rest; ensure you
update evt.setTotalFailed and evt.setFailedVmUuids to include both the
null-psUuid VMs and the failed replies from the existing replies loop.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`:
- Around line 26-30: APIRegisterVmInstanceFromMetadataMsg currently marks
metadataPath as required while metadataContent is optional, which conflicts with
the intended "one-of" API semantics; update the contract by removing the
`@APIParam`(required=true) on metadataPath (or both optional) and implement an API
interceptor (e.g., in the API interceptor for
APIRegisterVmInstanceFromMetadataMsg) that validates the mutual constraint:
ensure at least one of metadataContent or metadataPath is non-empty and
optionally enforce they are not both provided if mutual exclusion is desired;
reference the fields metadataContent and metadataPath and the class
APIRegisterVmInstanceFromMetadataMsg when adding the interceptor check.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java`:
- Around line 39-40: The slotCapacity method can return negative values when
lvSize <= SBLK_HEADER_SIZE; add an input lower-bound check at the start of
slotCapacity(long lvSize) to fail-fast (e.g., throw IllegalArgumentException) if
lvSize is less than or equal to SBLK_HEADER_SIZE with a clear message mentioning
lvSize and SBLK_HEADER_SIZE, otherwise proceed to compute the capacity as
before; this prevents negative slot/payload sizes from propagating.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Around line 16-19: Update VmMetadataDirtyVO so its vmInstanceUuid foreign key
targets the entity object base class rather than the VO: change the
`@ForeignKey`(parentEntityClass = VmInstanceVO.class, ...) on the vmInstanceUuid
field to parentEntityClass = VmInstanceEO.class and add the same `@EntityGraph`
definition used in VmMetadataFingerprintVO to VmMetadataDirtyVO so it matches
the other metadata entity's mapping and ensures proper DB-level cascade
behavior.
In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java`:
- Around line 33-34: VmInstanceState.Registering is declared and added to
offlineStates but lacks transactions(...) definitions in the static initializer,
causing nextState() to throw when ChangeVmMetaDataMsg triggers
changeVmStateInDb(); fix by adding the Registering.transactions(...) transitions
in the static init block mirroring the transition pattern used for Starting
(define valid events and target states for Registering, including
success/failure/abort paths and any terminal transitions), so the state machine
has explicit transitions for VmInstanceState.Registering.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3453-3486: The current
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg) only uses the first
connectedHostUuids.get(0) which misses metadata on other hosts; change it to
fan-out the msg to every connectedHostUuid returned by the SQL, invoking
getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self).handle(msg,
hostUuid, ...) for each host, collect/aggregate all
ScanVmInstanceMetadataFromPrimaryStorageReply results (merge metadata lists and
consolidate errors) and reply once all completions finish; similarly update the
cleanup path (the code around lines handling cleanup, also using
getHypervisorBackendFactoryByHostUuid and LocalStorageHypervisorBackend.handle)
to either locate the correct host by checking each host or iterate all hosts and
perform cleanup per-host, and ensure ordering/parallelism and error aggregation
are handled and bus.reply(msg, aggregatedReply) is sent only after all hosts
have completed.
- Around line 3418-3450: 当前实现随意选取 connectedHostUuids.get(0) 来处理
GetVmInstanceMetadataFromPrimaryStorageMsg,会在多主机环境下随机打到错误主机;请在
handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) 中使用
msg.getVmInstanceUuid() 作为查询条件,先查出承载该 VM(或其根卷/metadata)的主机 UUID(例如通过
VolumeVO/LocalStorageVolume/LocalStorageHostRefVO 的关联查询以 self.getUuid() 和
vmInstanceUuid 为条件),若未找到则按现有方式返回错误;找到后再用
getHypervisorBackendFactoryByHostUuid(hostUuid) /
LocalStorageHypervisorBackend.handle(...) 去调用指定主机处理而不是使用
connectedHostUuids.get(0) 随机选择主机。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 927-930: Two subclasses duplicate the storagePath field already
defined in AgentCommand, causing field shadowing and JSON serialization
ambiguity; remove the redundant storagePath declarations from the subclasses
(e.g., GetVmInstanceMetadataCmd and the other subclass that declares
storagePath) so they rely on the storagePath field inherited from AgentCommand,
and update any local references in handlers or serializers to use the inherited
field name rather than the now-removed subclass field.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.java`:
- Around line 32-42: 在 createResourceRef 方法里对 hostUuid 做非空校验:在使用
LocalStorageResourceRefVO 并调用 dbf.persist(ref) 之前检查传入的 hostUuid 参数,若为 null
则立即抛出一个明确的异常(例如 IllegalArgumentException 或适合项目的 API 异常类型)并带上说明性错误信息,避免将无 host
归属的 ref 写入数据库;否则按原有逻辑构造 LocalStorageResourceRefVO 并持久化。
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1953-1954: 当前代码使用 getUsableBackend() 随机选择 backend 导致可能与给定 hostUuid
不匹配;请改为按 hostUuid 获取对应的 backend(例如新增或使用类似 getBackendByHostUuid(hostUuid) /
findBackendForHost(hostUuid) 的方法)并将返回的 NfsPrimaryStorageBackend 用于
backend.handle(msg, hostUuid, ...),同时替换同样模式的其他 handler(处理
UpdateVmInstanceMetadataOnPrimaryStorageReply 的相关分支和评论中列举的相同模式处)以确保请求按 hostUuid
路由到正确的 backend。
- Around line 1945-1949: The helper getHostUuidFromVolume (used in
NfsPrimaryStorage) currently calls dbf.findByUuid() and may dereference the
returned VolumeVO causing an NPE when the volume UUID is missing; update
getHostUuidFromVolume to first check the result of
dbf.findByUuid(msg.getRootVolumeUuid()) for null and return null (or a clear
business error) instead of dereferencing, and ensure any code paths in
NfsPrimaryStorage that call getHostUuidFromVolume (the block around
reply.setError(...)) continue to handle a null return gracefully; reference
VolumeVO and VolumeSnapshotTreeBase patterns for null-check behavior.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2099-2100: The code currently forwards msg.getMetadataPath()
directly into ReadVmMetadataCmd.metadataPath; instead resolve and validate that
the requested path is inside the storage mount's metadata directory before
passing it to the agent. In NfsPrimaryStorageKVMBackend (where METADATA_DIR_NAME
and mountPath are available) compute the canonical/real path of the allowed
directory (Paths.get(mountPath, METADATA_DIR_NAME).toRealPath()), then resolve
the requested path against the metadata dir (or resolve the supplied path
toRealPath) and verify the resolved path startsWith the allowed directory; if
the check fails, reject the request (set an error/exception) instead of sending
the original metadataPath. Only send the validated/resolved in-dir path (or a
path relative to METADATA_DIR_NAME) to ReadVmMetadataCmd.metadataPath.
- Around line 2065-2072: The metadata command objects (e.g., WriteVmMetadataCmd
which extends NfsPrimaryStorageAgentCommand) are created and sent without the
primary storage uuid set, so the agent loses storage context; fix by either
sending them via the existing asyncHttpCall()/syncHttpCall() helpers that
populate uuid automatically or explicitly call
cmd.setUuid(msg.getPrimaryStorageUuid()) before sending (apply the change for
all metadata command constructions in this class).
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1794-1817: The handlers in PrimaryStorageBase
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg),
handle(ReadVmInstanceMetadataOnPrimaryStorageMsg),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) currently reply success
with no-op; change them to fail-fast by setting the reply to a failure and
including an explanatory error message so callers don't get silent success or
empty results: for each handler create the appropriate Reply instance, call
reply.setSuccess(false) and reply.setError("Operation <MSG_CLASS> not
implemented in PrimaryStorageBase; subclass must override") (using the message
class name or similar), then bus.reply(msg, reply). This ensures subclasses must
implement these methods or callers receive a clear error.
---
Minor comments:
In
`@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java`:
- Around line 18-19: 在 APIGetVmInstanceMetadataFromPrimaryStorageMsg
类中,vmInstanceUuid 字段当前只有裸 `@APIParam`,需在注解上补上资源校验以在 API 层拦截无效 VM UUID;修改
vmInstanceUuid 的 `@APIParam` 为包含 resourceType = VmInstanceVO.class
的形式,确保请求在进入主存储处理前校验 VM 资源存在性。
In
`@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy`:
- Around line 16-18: 在
APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy 中为字段名为 "success"
的描述补充明确语义(位于 name "success" / desc "" / type "boolean" 的条目),将 desc
填写为简短说明例如“操作是否成功;true 表示成功,false 表示失败并可能伴随 error 信息”,以消除歧义并与其它文档字段语义保持一致。
In
`@header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy`:
- Line 20: 在 APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy 中将空的
desc """""" 填写为一段简短的中文描述,说明该 API
的用途(例如:“扫描指定主存储上的虚拟机实例元数据,返回发现的元数据条目以供同步/校验”),确保在文件中定位到 desc
字段并替换为空字符串的地方(符号:desc 在
APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy)即可。
In
`@header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java`:
- Around line 18-19: In APIScanVmInstanceMetadataFromPrimaryStorageReply update
the setter setVmInstanceMetadata to normalize null inputs: if the passed
vmInstanceMetadata is null, assign an empty list to the vmInstanceMetadata field
(to match the class's default non-null semantics), otherwise assign the provided
list; reference the setVmInstanceMetadata method and the vmInstanceMetadata
field when making the change.
In
`@header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java`:
- Around line 15-16: The setter setVmInstanceMetadata currently assigns the
passed List directly and can set the field vmInstanceMetadata to null; change it
to guard against null by assigning an empty List when the argument is null
(e.g., use Collections.emptyList() or new ArrayList<>()) so vmInstanceMetadata
is never null and downstream iteration won't NPE. Ensure you update
setVmInstanceMetadata in class ScanVmInstanceMetadataFromPrimaryStorageReply to
perform this null-check/replace.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy`:
- Around line 29-31: The event documentation for
APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy leaves the "success" field
description empty; update the desc for the "success" property (the entry with
name "success") to a concise Chinese description such as indicating whether the
cleanup operation completed successfully (true) or failed (false), making sure
the wording matches existing doc style and clarifies the meaning and expected
boolean values.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy`:
- Around line 15-22: The ref path in the metadata doc incorrectly references
class org.zstack.header.vm.metadata.APIUpdateVmMetadataEvent.error; update that
path to org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error to
match the real event class name (APIUpdateVmInstanceMetadataEvent) and keep the
ref name "error" and clz ErrorCode.class unchanged so the reference points to
the correct event class.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java`:
- Line 17: The constant VmInstanceMetadataConstants.MAX_WRITE_SEQUENCE currently
uses the literal 0xFFFFFFFFFFFFFFFFL which is -1L as a signed long and therefore
misleading; either (A) if this is intended as a sentinel/wrap-around marker,
rename the constant to WRITE_SEQUENCE_WRAP_AROUND (or WRITE_SEQUENCE_SENTINEL)
and update all usages to reflect the sentinel semantics and adjust Javadoc, or
(B) if it should represent the maximum write sequence numeric bound, change the
value to Long.MAX_VALUE (or an appropriate positive upper bound) and update
Javadoc and any logic that depends on it; make sure to update all references to
the symbol (MAX_WRITE_SEQUENCE) in the codebase to the new name or handle the
new value appropriately.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java`:
- Around line 3-49: The class VmInstanceMetadataRegistrationSpec contains
corrupted JavaDoc (garbled characters like �? and broken HTML tags like �?/p>);
fix by converting the file to UTF-8 if needed and correcting the JavaDoc blocks
for the class and the field comments (zoneUuid, primaryStorageUuid, accountUuid,
oldPathIdentifier, newPathIdentifier) to valid text and well-formed HTML/JavaDoc
tags (use /** ... */ with <p>...</p> or plain paragraphs and proper list tags),
ensuring Chinese descriptions are readable and tags like </p>, <ul>, <li> are
properly closed and escaped where required.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java`:
- Around line 5-17: The Javadoc in VmMetadataCanonicalEvents contains garbled
characters due to encoding; open the source file for VmMetadataCanonicalEvents,
ensure the file is saved in UTF-8 encoding, and correct the corrupted Javadoc
text (fix lines mentioning “定义�?”, “消费�?”, “路径�?” etc.) so the comments read
properly (mentioning EventFacade.fire(), UpdateVmInstanceMetadataGC and
MetadataHealthCheckJob where relevant); re-run a build or IDE reindex to confirm
the Javadoc renders correctly.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java`:
- Around line 4-11: 修复 VmMetadataCategory 类的 Javadoc 中的乱码与错误闭合标签:在
VmMetadataCategory 顶部注释段落中将所有出现的“�?”替换为正确中文字符并修正 HTML 标签(例如把 “�?/p>” 改为 "</p>"
或直接使用空行分段,把 “�?/li>” 改为 "</li>" 或关闭相应的
<li>),确保枚举项说明对齐(REGULAR,TEMPLATE,TEMPLATE_CACHE)且标签成对匹配,或者简化为纯文本列出三项以避免 HTML
转义问题;保存后运行 javadoc 或 IDE 注释预览确认无乱码与未闭合标签。
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java`:
- Around line 3-15: The class-level Javadoc in VmMetadataConstants.java contains
garbled characters and a broken `@see` tag; open the Javadoc for the
VmMetadataConstants class and replace the corrupted sequences (e.g. "�?"
occurrences on line 4/6/10) with the intended text or proper punctuation (fix
Chinese characters/punctuation or convert to plain English), correct the
malformed `@see` closing tag from "/a>" to a proper </a> tag and ensure the
URL/anchor text is valid, and save the file with UTF-8 encoding so the comment
renders correctly during Javadoc generation.
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java`:
- Around line 208-213: The comment about allowed "metadata-related reads" is out
of sync with the whitelist in allowedOperations.addState for
VmInstanceState.Registering (currently only ChangeVmMetaDataMsg,
APIDestroyVmInstanceMsg, DestroyVmInstanceMsg); either update the comment to
remove the "metadata-related reads" phrase to reflect the actual allowed
messages, or add the appropriate read message class names to the addState call
(e.g., the project's VM metadata read/query message classes) so the whitelist
matches the comment, ensuring you update the same call to
allowedOperations.addState and keep VmInstanceState.Registering,
ChangeVmMetaDataMsg, APIDestroyVmInstanceMsg and DestroyVmInstanceMsg
consistent.
In
`@header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy`:
- Line 20: The API documentation file
APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy has an empty desc
string for the message doc; update the desc to a concise Chinese description of
the request body and purpose (e.g., explain what parameters are expected and
what the API returns) so the doc is self-descriptive—locate the desc """"""
entry in APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy and
replace the empty string with a short explanatory sentence in Chinese that
summarizes the request payload and intent.
In
`@header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy`:
- Around line 19-23: 在
APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy 中为字段名 "success"
补充描述,目前 desc 为空;将 desc 填为简短清晰的中文说明(例如 “操作是否成功,true 表示成功,false 表示失败”)以提高文档可读性,确保在
APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn 类/文件中更新该 success 字段的
desc 属性并保持风格与其它字段一致。
In
`@header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java`:
- Around line 10-13: 在 APICleanupVmInstanceMetadataEvent 类中将包装类型字段 totalCleaned
和 totalFailed 改为原始类型 long(避免装箱/拆箱开销与可空语义),并在字段声明处将 failedVmUuids 默认初始化为一个空集合(例如
Collections.emptyList() 或 new ArrayList<>());同时检查并调整任何 getter/setter
或序列化逻辑以匹配类型更改(totalCleaned、totalFailed、failedVmUuids)。
In `@header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java`:
- Around line 40-53: Add startup validation in the Interceptor that scans for
usages of the MetadataImpact annotation and enforces that when
MetadataImpact.value() != Impact.NONE the resolver() is non-empty and resolves
to an existing Spring bean (VmUuidFromApiResolver) via
ComponentLoader.getComponentByBeanName(); if the resolver is blank or the bean
lookup fails, throw a clear configuration/runtime exception to fail fast during
application startup so misconfigured annotations are detected early. Ensure the
check references MetadataImpact.resolver(), MetadataImpact.value(), Impact.NONE,
VmUuidFromApiResolver and uses ComponentLoader.getComponentByBeanName() during
the Interceptor initialization phase.
In `@header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java`:
- Around line 3-8: The DTO ResourceMetadata exposes a cryptic field name `vo`
and uses public fields; rename `vo` to a more descriptive identifier (e.g.,
`voData` or `voSnapshot`) and convert the public fields (resourceUuid,
vo/voData, systemTags, resourceConfigs) to private with standard getter/setter
methods on the ResourceMetadata class to match project conventions; update all
references/usages of ResourceMetadata accordingly (constructor, serializers,
callers) to use the new field name and accessors to avoid breaking behavior.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java`:
- Around line 7-11: Add Javadoc comments to the VmUuidFromApiResolver interface:
document the interface purpose and add Javadoc for the supports(APIMessage msg)
method describing what messages it accepts and what the boolean result means,
and for resolveVmUuids(APIMessage msg) describing that it extracts VM UUID(s)
from the given APIMessage and explain the return value (e.g., list of UUID
strings, empty list if none). Include `@param` and `@return` tags for both methods
to satisfy the coding standard.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java`:
- Around line 357-374: Modify the getVmInstanceMetadata and scanVmMetadata
simulator handlers to return configurable non-empty results instead of always
empty lists: read pre-populated entries from LocalStorageSimulatorConfig
(similar to readVmMetadataContent) and set them on
GetVmInstanceMetadataRsp.entries and ScanVmMetadataRsp.metadataEntries
respectively before calling reply(entity, rsp); also add/use collection fields
in LocalStorageSimulatorConfig to allow tests to inject DTOs for both endpoints
so serialization/mapping can be exercised.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java`:
- Around line 96-106: For each of the six handle(...) declarations
(UpdateVmInstanceMetadataOnPrimaryStorageMsg,
ReadVmInstanceMetadataOnPrimaryStorageMsg,
GetVmInstanceMetadataFromPrimaryStorageMsg,
ScanVmInstanceMetadataFromPrimaryStorageMsg,
CleanupVmInstanceMetadataOnPrimaryStorageMsg,
RebaseVolumeBackingFileOnPrimaryStorageMsg) add a Javadoc block that states (1)
when and how hostUuid should be chosen (caller vs candidate host vs null), (2)
which fields in the corresponding Reply type are mandatory for success and which
convey important metadata/errors, and (3) the failure semantics (what errors
mean, whether implementations should retry or return immediate failure and how
to populate the reply on error). Keep method signatures unchanged and ensure the
Javadoc follows project style and contains examples or expected return behavior
where helpful.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a027679b-eafa-4cc1-b2a7-008c6c12ce2c
⛔ 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 (95)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.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/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/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/ReadVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.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/VmInstanceMetadataSummary.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/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/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/VmInstanceMetadataRegistrationSpec.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.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/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/APICreateVolumeSnapshotGroupMsg.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.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/LocalStorageKvmMigrateVmFlow.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/LocalStorageVmMetadataPathReplacementExtension.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.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/NfsVmMetadataPathReplacementExtension.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/VmInstanceMetadataSummary.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.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 with no reviewable changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
| @GlobalConfigValidation(validValues = {"true", "false"}) | ||
| public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata"); |
There was a problem hiding this comment.
元数据总开关的配置名和现有常量对不上。
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java 里已经定义了 GLOBAL_CONFIG_METADATA_ENABLED = "vm.metadata.enabled",这里却注册成了 "vm.metadata"。两边如果分别被读取/写入,会变成两个独立配置项,开关状态不会联动。请统一到同一个 key;如果这里才是正确值,也要同步改掉常量和调用方。
🛠️ 按现有常量对齐的改法
- public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata");
+ public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata.enabled");🤖 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
140 - 141, VM metadata global config key is inconsistent: VmGlobalConfig
declares VM_METADATA with key "vm.metadata" but the canonical constant
VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED is
"vm.metadata.enabled"; make them identical. Update the GlobalConfig
instantiation in VmGlobalConfig (VM_METADATA) to use the same key as
VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED (or update the
constant and all call sites if you intend "vm.metadata" to be canonical) so
reads/writes reference the same global config key.
| List<String> vmUuids = msg.getVmUuids(); | ||
| List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); | ||
| for (String vmUuid : vmUuids) { | ||
| String psUuid = Q.New(VolumeVO.class) | ||
| .eq(VolumeVO_.vmInstanceUuid, vmUuid) | ||
| .eq(VolumeVO_.type, VolumeType.Root) | ||
| .select(VolumeVO_.primaryStorageUuid) | ||
| .findValue(); | ||
| if (psUuid == null) { | ||
| continue; | ||
| } | ||
|
|
||
| CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | ||
| cmsg.setPrimaryStorageUuid(psUuid); | ||
| cmsg.setVmUuid(vmUuid); | ||
| bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); | ||
| msgs.add(cmsg); | ||
| } | ||
|
|
||
| if (msgs.isEmpty()) { | ||
| evt.setTotalCleaned(0); | ||
| evt.setTotalFailed(0); | ||
| evt.setFailedVmUuids(new ArrayList<>()); | ||
| bus.publish(evt); | ||
| return; | ||
| } | ||
|
|
||
| bus.send(msgs, new CloudBusListCallBack(msg) { | ||
| @Override | ||
| public void run(List<MessageReply> replies) { | ||
| int totalCleaned = 0; | ||
| int totalFailed = 0; | ||
| List<String> failedVmUuids = new ArrayList<>(); | ||
| for (int i = 0; i < replies.size(); i++) { | ||
| MessageReply r = replies.get(i); | ||
| if (r.isSuccess()) { | ||
| totalCleaned++; | ||
| } else { | ||
| totalFailed++; | ||
| failedVmUuids.add(msgs.get(i).getVmUuid()); | ||
| } | ||
| } | ||
| evt.setTotalCleaned(totalCleaned); | ||
| evt.setTotalFailed(totalFailed); | ||
| evt.setFailedVmUuids(failedVmUuids); | ||
| bus.publish(evt); |
There was a problem hiding this comment.
不要把无法定位主存储的 VM 静默丢掉。
psUuid == null 时这里直接 continue,后面的 totalCleaned/totalFailed 只统计真正发出去的消息。这样请求里的这部分 VM 既不算成功也不算失败,返回值会少账,调用方拿不到真实清理结果。
🛠 建议修改
APICleanupVmInstanceMetadataEvent evt = new APICleanupVmInstanceMetadataEvent(msg.getId());
List<String> vmUuids = msg.getVmUuids();
List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>();
+ List<String> unresolvedVmUuids = new ArrayList<>();
for (String vmUuid : vmUuids) {
String psUuid = Q.New(VolumeVO.class)
.eq(VolumeVO_.vmInstanceUuid, vmUuid)
.eq(VolumeVO_.type, VolumeType.Root)
.select(VolumeVO_.primaryStorageUuid)
.findValue();
if (psUuid == null) {
+ unresolvedVmUuids.add(vmUuid);
continue;
}
CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg();
cmsg.setPrimaryStorageUuid(psUuid);
@@
if (msgs.isEmpty()) {
evt.setTotalCleaned(0);
- evt.setTotalFailed(0);
- evt.setFailedVmUuids(new ArrayList<>());
+ evt.setTotalFailed(unresolvedVmUuids.size());
+ evt.setFailedVmUuids(unresolvedVmUuids);
bus.publish(evt);
return;
}
bus.send(msgs, new CloudBusListCallBack(msg) {
`@Override`
public void run(List<MessageReply> replies) {
int totalCleaned = 0;
- int totalFailed = 0;
- List<String> failedVmUuids = new ArrayList<>();
+ int totalFailed = unresolvedVmUuids.size();
+ List<String> failedVmUuids = new ArrayList<>(unresolvedVmUuids);
for (int i = 0; i < replies.size(); i++) {
MessageReply r = replies.get(i);
if (r.isSuccess()) {
totalCleaned++;
} else {📝 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<String> vmUuids = msg.getVmUuids(); | |
| List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); | |
| for (String vmUuid : vmUuids) { | |
| String psUuid = Q.New(VolumeVO.class) | |
| .eq(VolumeVO_.vmInstanceUuid, vmUuid) | |
| .eq(VolumeVO_.type, VolumeType.Root) | |
| .select(VolumeVO_.primaryStorageUuid) | |
| .findValue(); | |
| if (psUuid == null) { | |
| continue; | |
| } | |
| CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | |
| cmsg.setPrimaryStorageUuid(psUuid); | |
| cmsg.setVmUuid(vmUuid); | |
| bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); | |
| msgs.add(cmsg); | |
| } | |
| if (msgs.isEmpty()) { | |
| evt.setTotalCleaned(0); | |
| evt.setTotalFailed(0); | |
| evt.setFailedVmUuids(new ArrayList<>()); | |
| bus.publish(evt); | |
| return; | |
| } | |
| bus.send(msgs, new CloudBusListCallBack(msg) { | |
| @Override | |
| public void run(List<MessageReply> replies) { | |
| int totalCleaned = 0; | |
| int totalFailed = 0; | |
| List<String> failedVmUuids = new ArrayList<>(); | |
| for (int i = 0; i < replies.size(); i++) { | |
| MessageReply r = replies.get(i); | |
| if (r.isSuccess()) { | |
| totalCleaned++; | |
| } else { | |
| totalFailed++; | |
| failedVmUuids.add(msgs.get(i).getVmUuid()); | |
| } | |
| } | |
| evt.setTotalCleaned(totalCleaned); | |
| evt.setTotalFailed(totalFailed); | |
| evt.setFailedVmUuids(failedVmUuids); | |
| bus.publish(evt); | |
| List<String> vmUuids = msg.getVmUuids(); | |
| List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); | |
| List<String> unresolvedVmUuids = new ArrayList<>(); | |
| for (String vmUuid : vmUuids) { | |
| String psUuid = Q.New(VolumeVO.class) | |
| .eq(VolumeVO_.vmInstanceUuid, vmUuid) | |
| .eq(VolumeVO_.type, VolumeType.Root) | |
| .select(VolumeVO_.primaryStorageUuid) | |
| .findValue(); | |
| if (psUuid == null) { | |
| unresolvedVmUuids.add(vmUuid); | |
| continue; | |
| } | |
| CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | |
| cmsg.setPrimaryStorageUuid(psUuid); | |
| cmsg.setVmUuid(vmUuid); | |
| bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); | |
| msgs.add(cmsg); | |
| } | |
| if (msgs.isEmpty()) { | |
| evt.setTotalCleaned(0); | |
| evt.setTotalFailed(unresolvedVmUuids.size()); | |
| evt.setFailedVmUuids(unresolvedVmUuids); | |
| bus.publish(evt); | |
| return; | |
| } | |
| bus.send(msgs, new CloudBusListCallBack(msg) { | |
| `@Override` | |
| public void run(List<MessageReply> replies) { | |
| int totalCleaned = 0; | |
| int totalFailed = unresolvedVmUuids.size(); | |
| List<String> failedVmUuids = new ArrayList<>(unresolvedVmUuids); | |
| for (int i = 0; i < replies.size(); i++) { | |
| MessageReply r = replies.get(i); | |
| if (r.isSuccess()) { | |
| totalCleaned++; | |
| } else { | |
| totalFailed++; | |
| failedVmUuids.add(msgs.get(i).getVmUuid()); | |
| } | |
| } | |
| evt.setTotalCleaned(totalCleaned); | |
| evt.setTotalFailed(totalFailed); | |
| evt.setFailedVmUuids(failedVmUuids); | |
| bus.publish(evt); | |
| } | |
| }); |
🤖 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/VmInstanceManagerImpl.java`
around lines 2878 - 2923, The code in VmInstanceManagerImpl silently skips VMs
with psUuid == null so they aren't counted in evt totals; instead, when
iterating vmUuids (the loop that builds msgs and checks psUuid), mark those with
null primary storage as failures by incrementing a failure counter and adding
the vmUuid to the failedVmUuids collection that will be published via evt (or
accumulate them and merge with failures collected from the
CloudBusListCallBack), then proceed to build/send msgs for the rest; ensure you
update evt.setTotalFailed and evt.setFailedVmUuids to include both the
null-psUuid VMs and the failed replies from the existing replies loop.
| private void handle(APIUpdateVmInstanceMetadataMsg msg) { | ||
| APIUpdateVmInstanceMetadataEvent event = new APIUpdateVmInstanceMetadataEvent(msg.getId()); | ||
| if (vmMetadataDirtyMarker != null) { | ||
| for (String vmUuid : msg.getVmUuids()) { | ||
| vmMetadataDirtyMarker.markDirty(vmUuid, true); | ||
| } | ||
| } | ||
| bus.publish(event); |
There was a problem hiding this comment.
能力缺失时不要直接发布成功事件。
VmMetadataDirtyService 是可选注入,但未注入时这里仍然返回成功。结果就是 API 看起来成功,实际上没有任何 VM 被标脏,调用方无法感知更新根本没有生效。
🛠 建议修改
private void handle(APIUpdateVmInstanceMetadataMsg msg) {
APIUpdateVmInstanceMetadataEvent event = new APIUpdateVmInstanceMetadataEvent(msg.getId());
- if (vmMetadataDirtyMarker != null) {
- for (String vmUuid : msg.getVmUuids()) {
- vmMetadataDirtyMarker.markDirty(vmUuid, true);
- }
+ if (vmMetadataDirtyMarker == null) {
+ event.setError(operr("vm metadata dirty service is unavailable"));
+ bus.publish(event);
+ return;
+ }
+
+ for (String vmUuid : msg.getVmUuids()) {
+ vmMetadataDirtyMarker.markDirty(vmUuid, true);
}
bus.publish(event);
}🤖 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/VmInstanceManagerImpl.java`
around lines 2928 - 2935, The handler method
handle(APIUpdateVmInstanceMetadataMsg msg) currently always publishes a success
APIUpdateVmInstanceMetadataEvent even when the optional vmMetadataDirtyMarker is
null; change it so that if vmMetadataDirtyMarker == null you do not publish a
success event but instead set the event to an error (or publish a failed event)
indicating the metadata-dirty capability is unavailable. Concretely: inside
handle(APIUpdateVmInstanceMetadataMsg) check vmMetadataDirtyMarker for null
before looping over msg.getVmUuids(); if null, populate the
APIUpdateVmInstanceMetadataEvent with an error/failure code and descriptive
message about the missing VmMetadataDirtyService and then publish that error
event; otherwise perform vmMetadataDirtyMarker.markDirty(vmUuid, true) for each
vmUuid and publish the success event as before. Ensure you reference
vmMetadataDirtyMarker, vmUuid, vmMetadataDirtyMarker.markDirty(...), and
bus.publish(event) when making the change.
| @APIParam(required = false) | ||
| private String metadataContent; | ||
|
|
||
| @APIParam | ||
| private String metadataPath; |
There was a problem hiding this comment.
metadataContent/metadataPath 约束与接口语义冲突。
Line 26 到 Line 30 里 metadataPath 被定义为必填,这与接口文档“二选一”冲突,会导致仅传 metadataContent 的请求被参数校验拒绝。请统一契约(要么改成真正二选一并在拦截器校验,要么修改文档描述)。
建议修复(示例)
- `@APIParam`
+ `@APIParam`(required = false)
private String metadataPath;// 在 API interceptor 中增加互斥/至少一个校验:
// 1) metadataContent 与 metadataPath 不能同时为空
// 2) 可按需要限制不能同时都传🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`
around lines 26 - 30, APIRegisterVmInstanceFromMetadataMsg currently marks
metadataPath as required while metadataContent is optional, which conflicts with
the intended "one-of" API semantics; update the contract by removing the
`@APIParam`(required=true) on metadataPath (or both optional) and implement an API
interceptor (e.g., in the API interceptor for
APIRegisterVmInstanceFromMetadataMsg) that validates the mutual constraint:
ensure at least one of metadataContent or metadataPath is non-empty and
optionally enforce they are not both provided if mutual exclusion is desired;
reference the fields metadataContent and metadataPath and the class
APIRegisterVmInstanceFromMetadataMsg when adding the interceptor check.
| public static long slotCapacity(long lvSize) { | ||
| return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096; |
There was a problem hiding this comment.
为 slotCapacity 增加入参下界校验,避免负容量
Line 39-40 在 lvSize <= SBLK_HEADER_SIZE 时会得到负值,后续可能传播为负的 slot/payload 容量。建议在入口直接 fail-fast。
🛡️ 建议修复
public static long slotCapacity(long lvSize) {
+ if (lvSize <= SBLK_HEADER_SIZE) {
+ throw new IllegalArgumentException(String.format(
+ "lvSize must be greater than header size, lvSize=%d, headerSize=%d",
+ lvSize, SBLK_HEADER_SIZE));
+ }
return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static long slotCapacity(long lvSize) { | |
| return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096; | |
| public static long slotCapacity(long lvSize) { | |
| if (lvSize <= SBLK_HEADER_SIZE) { | |
| throw new IllegalArgumentException(String.format( | |
| "lvSize must be greater than header size, lvSize=%d, headerSize=%d", | |
| lvSize, SBLK_HEADER_SIZE)); | |
| } | |
| return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096; | |
| } |
🤖 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/VmMetadataConstants.java`
around lines 39 - 40, The slotCapacity method can return negative values when
lvSize <= SBLK_HEADER_SIZE; add an input lower-bound check at the start of
slotCapacity(long lvSize) to fail-fast (e.g., throw IllegalArgumentException) if
lvSize is less than or equal to SBLK_HEADER_SIZE with a clear message mentioning
lvSize and SBLK_HEADER_SIZE, otherwise proceed to compute the capacity as
before; this prevents negative slot/payload sizes from propagating.
plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
Show resolved
Hide resolved
| final NfsPrimaryStorageBackend backend = getUsableBackend(); | ||
| backend.handle(msg, hostUuid, new ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply>(msg) { |
There was a problem hiding this comment.
这里应该按 hostUuid 选 backend,而不是随便拿一个可用 backend。
这些分支都已经拿到了明确的 hostUuid,但随后还是调用 getUsableBackend()。该方法只会返回当前主存储上的任意一个可用 backend;一旦同一个 NFS 主存储挂到了不同 hypervisor cluster,这里的 backend 就可能和 hostUuid 不匹配,最终把请求派发到错误的实现。
💡 建议
- final NfsPrimaryStorageBackend backend = getUsableBackend();
+ final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid);
+ if (backend == null) {
+ reply.setError(operr("no backend found for host[uuid:%s]", hostUuid));
+ bus.reply(msg, reply);
+ return;
+ }其余几个同样模式的 handler 也建议一起改掉。
Also applies to: 1988-1990, 2016-2018, 2044-2046, 2072-2074, 2091-2107
🤖 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 1953 - 1954, 当前代码使用 getUsableBackend() 随机选择 backend 导致可能与给定
hostUuid 不匹配;请改为按 hostUuid 获取对应的 backend(例如新增或使用类似
getBackendByHostUuid(hostUuid) / findBackendForHost(hostUuid) 的方法)并将返回的
NfsPrimaryStorageBackend 用于 backend.handle(msg, hostUuid, ...),同时替换同样模式的其他
handler(处理 UpdateVmInstanceMetadataOnPrimaryStorageReply
的相关分支和评论中列举的相同模式处)以确保请求按 hostUuid 路由到正确的 backend。
| WriteVmMetadataCmd cmd = new WriteVmMetadataCmd(); | ||
| cmd.metadata = msg.getMetadata(); | ||
| cmd.metadataPath = metadataPath; | ||
| cmd.vmInstanceUuid = msg.getVmInstanceUuid(); | ||
| cmd.vmInstanceName = msg.getVmInstanceName(); | ||
| cmd.architecture = msg.getArchitecture(); | ||
| cmd.schemaVersion = msg.getSchemaVersion(); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
FILE='plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java'
rg -n -C2 'class (WriteVmMetadataCmd|GetVmInstanceMetadataCmd|ScanVmMetadataCmd|CleanupVmMetadataCmd)\b|extends NfsPrimaryStorageAgentCommand|setUuid\(' "$FILE"Repository: MatheMatrix/zstack
Length of output: 7074
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '2060,2080p'Repository: MatheMatrix/zstack
Length of output: 1442
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '2130,2180p'Repository: MatheMatrix/zstack
Length of output: 2818
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '2170,2185p'Repository: MatheMatrix/zstack
Length of output: 953
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '2255,2270p'Repository: MatheMatrix/zstack
Length of output: 925
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '2285,2300p'Repository: MatheMatrix/zstack
Length of output: 964
🏁 Script executed:
rg -n 'KVMHostAsyncHttpCallMsg|asyncHttpCall|syncHttpCall' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | head -30Repository: MatheMatrix/zstack
Length of output: 3609
🏁 Script executed:
rg -n 'class KVMHostAsyncHttpCallMsg' --type javaRepository: MatheMatrix/zstack
Length of output: 198
🏁 Script executed:
rg -n 'asyncHttpCall|syncHttpCall' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java -B2 -A5 | head -50Repository: MatheMatrix/zstack
Length of output: 3058
🏁 Script executed:
rg -n 'private.*asyncHttpCall|protected.*asyncHttpCall|public.*asyncHttpCall' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java -A15 | head -60Repository: MatheMatrix/zstack
Length of output: 1359
🏁 Script executed:
rg -n 'protected void asyncHttpCall|private void asyncHttpCall|public void asyncHttpCall' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/ -A10 | head -80Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
fd -e java NfsPrimaryStorage | xargs rg -l 'void asyncHttpCall' | head -5Repository: MatheMatrix/zstack
Length of output: 164
🏁 Script executed:
rg -n 'protected.*syncHttpCall|private.*syncHttpCall' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java -A10 | head -50Repository: MatheMatrix/zstack
Length of output: 1870
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '1775,1810p'Repository: MatheMatrix/zstack
Length of output: 1955
🏁 Script executed:
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java | sed -n '1812,1830p'Repository: MatheMatrix/zstack
Length of output: 1176
🏁 Script executed:
# Check if metadata commands have uuid field or use it
rg -n 'uuid' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java | grep -E 'WriteVmMetadataCmd|GetVmInstanceMetadataCmd|ScanVmMetadataCmd|CleanupVmMetadataCmd' -A10 -B10Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Check if NfsPrimaryStorageAgentCommand base class uses uuid
cat -n plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java | sed -n '1,50p'Repository: MatheMatrix/zstack
Length of output: 1858
🏁 Script executed:
# Verify if uuid is actually used/required in agent command processing
rg -n 'getUuid|uuid' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java | grep -E 'WriteVmMetadataCmd|GetVmInstanceMetadataCmd|ScanVmMetadataCmd|CleanupVmMetadataCmd|NfsPrimaryStorageAgentCommand' -A3 -B3Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Check what the agent actually does with uuid
rg -n 'uuid' plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java | head -20Repository: MatheMatrix/zstack
Length of output: 372
🏁 Script executed:
# Check KVM agent side to see if it uses uuid from command
rg -n 'WriteVmMetadata|primaryStorageUuid' plugin/kvm/src/main/java/ -A3 -B3 | head -60Repository: MatheMatrix/zstack
Length of output: 5940
🏁 Script executed:
# Check if there's agent-side handling for these metadata commands
fd -e py '.*' plugin/kvm | xargs grep -l 'WriteVmMetadata\|GetVmInstanceMetadata\|ScanVmMetadata\|CleanupVmMetadata' 2>/dev/null | head -5Repository: MatheMatrix/zstack
Length of output: 44
🏁 Script executed:
# Check if primaryStorageUuid is used in any agent command handling
rg -n 'primaryStorageUuid' plugin/kvm/src/main/java/ -A2 -B2 | head -40Repository: MatheMatrix/zstack
Length of output: 4061
🏁 Script executed:
# Look for metadata command implementations in the codebase
find . -name '*.py' -path '*/kvm/*' | xargs grep -l 'write_vm_metadata\|WriteVmMetadata' 2>/dev/null | head -3Repository: MatheMatrix/zstack
Length of output: 44
这些新 metadata 命令缺少主存储 uuid 的设置。
同文件的 NFS 命令要么通过 asyncHttpCall() / syncHttpCall() helper 在发送前自动设置 uuid,要么如 Line 2296 那样显式调用 setUuid(...)。这里四个 metadata 命令都绕过了 helper,且没有给命令对象设置 uuid;由于它们继承自 NfsPrimaryStorageAgentCommand,agent 端会拿不到主存储上下文。
建议改为:
- 使用现有的
asyncHttpCall()helper,或 - 在发送前显式调用
cmd.setUuid(msg.getPrimaryStorageUuid())
涉及位置:lines 2065-2072、2135-2137、2175-2176、2261-2262
🤖 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 2065 - 2072, The metadata command objects (e.g., WriteVmMetadataCmd
which extends NfsPrimaryStorageAgentCommand) are created and sent without the
primary storage uuid set, so the agent loses storage context; fix by either
sending them via the existing asyncHttpCall()/syncHttpCall() helpers that
populate uuid automatically or explicitly call
cmd.setUuid(msg.getPrimaryStorageUuid()) before sending (apply the change for
all metadata command constructions in this class).
| ReadVmMetadataCmd cmd = new ReadVmMetadataCmd(); | ||
| cmd.metadataPath = msg.getMetadataPath(); |
There was a problem hiding this comment.
不要把 metadataPath 原样透传给 agent。
这里和 Update/Get/Scan/Cleanup 分支不一样,完全信任了上游传入的绝对路径。只要调用方把路径带错或被污染,请求就能越过 vm_metadata 目录边界去读任意 NFS 文件;至少应在 backend 里校验它必须落在 <mountPath>/<METADATA_DIR_NAME>/ 之下。
🤖 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 2099 - 2100, The code currently forwards msg.getMetadataPath()
directly into ReadVmMetadataCmd.metadataPath; instead resolve and validate that
the requested path is inside the storage mount's metadata directory before
passing it to the agent. In NfsPrimaryStorageKVMBackend (where METADATA_DIR_NAME
and mountPath are available) compute the canonical/real path of the allowed
directory (Paths.get(mountPath, METADATA_DIR_NAME).toRealPath()), then resolve
the requested path against the metadata dir (or resolve the supplied path
toRealPath) and verify the resolved path startsWith the allowed directory; if
the check fails, reject the request (set an error/exception) instead of sending
the original metadataPath. Only send the validated/resolved in-dir path (or a
path relative to METADATA_DIR_NAME) to ReadVmMetadataCmd.metadataPath.
| protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(ReadVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| ReadVmInstanceMetadataOnPrimaryStorageReply reply = new ReadVmInstanceMetadataOnPrimaryStorageReply(); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { | ||
| GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { | ||
| ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); | ||
| bus.reply(msg, reply); | ||
| } | ||
|
|
||
| protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { | ||
| CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); | ||
| bus.reply(msg, reply); | ||
| } |
There was a problem hiding this comment.
不要在基类里把 VM metadata 操作默默当成功处理。
这些 handler 现在已经被本类的新路由直接调用了。对任何没有 override 的 PrimaryStorageBase 子类,Update/Read/Get/Scan/Cleanup 都会返回“成功”,但实际上什么都没做;其中 Get/Scan 还会把空结果直接回给调用方。这里更安全的默认行为应该是 fail-fast,而不是 no-op 成功。
🤖 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 1794 - 1817, The handlers in PrimaryStorageBase
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg),
handle(ReadVmInstanceMetadataOnPrimaryStorageMsg),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) currently reply success
with no-op; change them to fail-fast by setting the reply to a failure and
including an explanatory error message so callers don't get silent success or
empty results: for each handler create the appropriate Reply instance, call
reply.setSuccess(false) and reply.setError("Operation <MSG_CLASS> not
implemented in PrimaryStorageBase; subclass must override") (using the message
class name or similar), then bus.reply(msg, reply). This ensures subclasses must
implement these methods or callers receive a clear error.
9d6a67e to
2ef5d1f
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (10)
header/src/main/java/org/zstack/header/vm/VmInstanceState.java (1)
33-34:⚠️ Potential issue | 🔴 Critical
Registering状态缺少状态转换定义,将导致运行时异常
Registering已添加到枚举中(第 34 行)并加入offlineStates集合(第 56 行),但静态初始化块中没有对应的Registering.transactions(...)定义。根据 AI 摘要,
Registering状态允许ChangeVmMetaDataMsg和销毁操作(APIDestroyVmInstanceMsg、DestroyVmInstanceMsg)。当这些操作触发状态迁移并调用nextState()时,会在第 238 行抛出CloudRuntimeException,因为transactionMap为空。建议参考其他离线状态(如
Created、Stopped)的模式,至少添加destroying和destroyed事件的转换路径。🔧 建议添加 Registering 状态转换定义
在静态初始化块中(约第 193 行
Crashed.transactions(...)之后)添加:+ Registering.transactions( + new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying), + new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed) + );#!/bin/bash # 验证 Registering 状态的使用场景,确认需要支持的状态转换事件 # 1) 查找 Registering 状态的设置点 rg -nP --type=java '\bVmInstanceState\.Registering\b' -C3 # 2) 查找 allowedOperations 中 Registering 允许的操作 rg -nP --type=java 'Registering.*allowedOperations|allowedOperations.*Registering' -C5 # 3) 检查是否有代码对 Registering 状态调用 nextState rg -nP --type=java 'Registering.*nextState|getState\(\)\.nextState' -C3🤖 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 33 - 34, VmInstanceState 中新增的枚举常量 Registering 缺少在静态初始化块中的 transactions(...) 定义,导致调用 nextState() 时 transactionMap 为空并抛出异常;请在静态初始化块(紧接在 Crashed.transactions(...) 之后)为 Registering 添加与其他 offline 状态(如 Created、Stopped)类似的转换定义,至少包含对 ChangeVmMetaDataMsg、APIDestroyVmInstanceMsg 和 DestroyVmInstanceMsg 的转换路径(例如到 destroying 和 destroyed 事件),确保 Registering.transactions(...) 将这些事件映射到正确的目标状态以避免运行时异常。compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java (2)
2928-2935:⚠️ Potential issue | 🟠 Major能力缺失时不要发布成功事件。
Line 2930 之后
vmMetadataDirtyMarker == null会把这次更新降级成静默 no-op,但 Line 2935 仍然发布成功事件;这样 API 看起来成功,实际上没有任何 VM 被标脏。🛠 建议修改
private void handle(APIUpdateVmInstanceMetadataMsg msg) { APIUpdateVmInstanceMetadataEvent event = new APIUpdateVmInstanceMetadataEvent(msg.getId()); - if (vmMetadataDirtyMarker != null) { - for (String vmUuid : msg.getVmUuids()) { - vmMetadataDirtyMarker.markDirty(vmUuid, true); - } + if (vmMetadataDirtyMarker == null) { + event.setError(operr("vm metadata dirty service is unavailable")); + bus.publish(event); + return; + } + + for (String vmUuid : msg.getVmUuids()) { + vmMetadataDirtyMarker.markDirty(vmUuid, true); } bus.publish(event); }🤖 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/VmInstanceManagerImpl.java` around lines 2928 - 2935, The method handle(APIUpdateVmInstanceMetadataMsg msg) currently marks VMs dirty only when vmMetadataDirtyMarker != null but always publishes a success event via bus.publish(event); change this so that if vmMetadataDirtyMarker is null you do not publish the success APIUpdateVmInstanceMetadataEvent — instead construct and publish a failure event (or set an error on APIUpdateVmInstanceMetadataEvent) indicating the capability is missing, and only publish the success event after marking at least one vm dirty; touch vmMetadataDirtyMarker, APIUpdateVmInstanceMetadataEvent (event) and bus.publish(...) to implement the check and failure publishing.
2878-2923:⚠️ Potential issue | 🟠 Major不要把无法定位主存储的 VM 静默丢掉。
Line 2886 这里
psUuid == null直接continue,会让这部分 VM 既不记成功也不记失败;当全部 VM 都落到这个分支时,Line 2898-Line 2900 还会返回0/0,调用方拿不到真实清理结果。🛠 建议修改
List<String> vmUuids = msg.getVmUuids(); List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); + List<String> unresolvedVmUuids = new ArrayList<>(); for (String vmUuid : vmUuids) { String psUuid = Q.New(VolumeVO.class) .eq(VolumeVO_.vmInstanceUuid, vmUuid) .eq(VolumeVO_.type, VolumeType.Root) .select(VolumeVO_.primaryStorageUuid) .findValue(); if (psUuid == null) { + unresolvedVmUuids.add(vmUuid); continue; } CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); cmsg.setPrimaryStorageUuid(psUuid); @@ if (msgs.isEmpty()) { evt.setTotalCleaned(0); - evt.setTotalFailed(0); - evt.setFailedVmUuids(new ArrayList<>()); + evt.setTotalFailed(unresolvedVmUuids.size()); + evt.setFailedVmUuids(unresolvedVmUuids); bus.publish(evt); return; } bus.send(msgs, new CloudBusListCallBack(msg) { `@Override` public void run(List<MessageReply> replies) { int totalCleaned = 0; - int totalFailed = 0; - List<String> failedVmUuids = new ArrayList<>(); + int totalFailed = unresolvedVmUuids.size(); + List<String> failedVmUuids = new ArrayList<>(unresolvedVmUuids); for (int i = 0; i < replies.size(); i++) { MessageReply r = replies.get(i); if (r.isSuccess()) { totalCleaned++; } else {🤖 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/VmInstanceManagerImpl.java` around lines 2878 - 2923, 当前对每个 vmUuid 在 VmInstanceManagerImpl 中查找 root 盘主存储时如果 psUuid == null 使用了 continue,导致这些 VM 被静默丢弃且不计入成功/失败统计;请修改生成 msgs 的逻辑:在循环中如果 psUuid 为 null 则不要 continue,而是记录该 vmUuid 到一个 missedPrimaryStorage 或 failedVmUuids 临时列表(或直接增加一个 missedCount),以便后续在返回事件时将这些 vm 标记为失败并计入 totalFailed;保留对 CleanupVmInstanceMetadataOnPrimaryStorageMsg 列表 msgs 的构建逻辑不变,但在处理 msgs.isEmpty() 分支和最终 bus.send 回调时合并这些因找不到主存储的 vmUuid(唯一标识符 vmUuid)到 evt.setFailedVmUuids(...) 并调整 evt.setTotalFailed(...) 的值(比如 totalFailed += missedCount)。compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java (1)
140-141:⚠️ Potential issue | 🟠 Major请把元数据总开关键统一到同一个 key。
如果元数据模块里仍以
vm.metadata.enabled作为 canonical key,这里的vm.metadata会把读写拆成两个独立配置项,开关状态不会联动。建议先核对
VmInstanceMetadataConstants里的常量值;两边应该完全一致。#!/bin/bash set -e echo "== VmGlobalConfig ==" sed -n '138,144p' compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java echo echo "== VmInstanceMetadataConstants ==" fd 'VmInstanceMetadataConstants.java$' header/src/main/java/org/zstack/header/vm/metadata -x sed -n '1,120p' {} echo echo "== Cross-file grep ==" rg -n "GLOBAL_CONFIG_METADATA_ENABLED|VM_METADATA =" \ compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java \ header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java🤖 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 140 - 141, VmGlobalConfig defines VM_METADATA with key "vm.metadata" which conflicts with the canonical key used in VmInstanceMetadataConstants (GLOBAL_CONFIG_METADATA_ENABLED); update the GlobalConfig instantiation in class VmGlobalConfig so the key string matches VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED (e.g. change "vm.metadata" to the exact constant value, such as "vm.metadata.enabled"), keeping the `@GlobalConfigValidation`(validValues = {"true","false"}) intact so reads/writes are on the same config item and the toggle state stays consistent across both classes.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (2)
3453-3486:⚠️ Potential issue | 🔴 Critical
scan/cleanup不能只打到第一台在线host。LocalStorage 的 metadata 分散在各
host本地目录上。现在先查出所有 connected hosts,随后仍然只用connectedHostUuids.get(0):scan会天然漏扫其他host,cleanup也会随机打到错误host。scan需要 fan-out 到所有 connected hosts 并聚合结果;cleanup至少要先定位 metadata 所属host,或者遍历所有 connected hosts 执行。Also applies to: 3488-3524
🤖 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 3453 - 3486, The current handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) only picks connectedHostUuids.get(0), causing scan/cleanup to run against a single host; change it to iterate over all connectedHostUuids, call the hypervisor backend via getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self).handle(msg, hostUuid, ...) for each host, aggregate successful ScanVmInstanceMetadataFromPrimaryStorageReply results and merge errors (or fail only if all hosts fail), and only then bus.reply(msg, aggregatedReply); for cleanup paths (the same code area using connectedHostUuids and LocalStorageHypervisorBackend.handle) either determine the correct host that owns the metadata before calling or iterate all connected hosts and attempt cleanup on each, collecting outcomes and reporting an aggregated result. Ensure concurrent callbacks are synchronized (countdown/AtomicInteger/Completion) so reply is sent once all host callbacks complete.
3418-3451:⚠️ Potential issue | 🔴 Critical不要随机选一台在线
host处理单 VM 的 metadata 查询。这里完全没用
msg.getVmInstanceUuid(),而是直接取connectedHostUuids.get(0)。LocalStorage 的 metadata 跟根卷绑定在具体host上;多host场景下这会命中错误主机,导致查询失败或结果随数据库返回顺序漂移。应该先根据vmInstanceUuid/根卷定位承载 metadata 的host,再把请求发到该host。🤖 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 3418 - 3451, The handler currently picks an arbitrary connected host (connectedHostUuids.get(0)) instead of locating the host that actually holds the VM's metadata; change handle(GetVmInstanceMetadataFromPrimaryStorageMsg) to first use msg.getVmInstanceUuid() to find the host that contains the VM's root volume/metadata (query VolumeVO/VolumeInventory for root volume where vmInstanceUuid = msg.getVmInstanceUuid() and primaryStorageUuid = self.getUuid(), then join to LocalStorageHostRefVO/HostVO to get the hostUuid with HostStatus.Connected), and if found use that hostUuid with getHypervisorBackendFactoryByHostUuid(...) and bkd.handle(...); if no such host is found return an error (or optionally fall back to the existing connected-host logic) so requests are routed to the host that actually stores the metadata.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1794-1817:⚠️ Potential issue | 🟠 Major基类默认实现不应静默返回成功。
这些 handler 已经通过
handleLocalMessage()暴露给所有 primary storage 子类了;如果某个存储类型没有 override,这里会把未实现的 metadata 操作当成成功处理,Get/Scan还会回一个“看起来合法”的空结果,调用方无法区分“未实现”和“确实没有 metadata”。这里更安全的默认行为应该是直接失败,强制子类显式实现。plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (2)
1897-1906:⚠️ Potential issue | 🟠 Major先兜住查不到卷的场景。
Line 1898 的
dbf.findByUuid()可能返回null,但后面马上就解引用vol.getVmInstanceUuid()了。UpdateVmInstanceMetadataOnPrimaryStorageMsg新增了对这个 helper 的调用;一旦rootVolumeUuid失效,这里会先抛 NPE,走不到后面的错误回复。建议修改
private String getHostUuidFromVolume(String volumeUuid) { VolumeVO vol = dbf.findByUuid(volumeUuid, VolumeVO.class); + if (vol == null) { + return ""; + } String hostUuid = "";Based on learnings, "在ZStack项目的VolumeSnapshotTreeBase类中,当通过dbf.findByUuid()方法获取VolumeVO对象时,需要进行null检查,因为该方法可能在找不到对应记录时返回null,直接使用可能导致NullPointerException。"
🤖 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 1897 - 1906, The helper getHostUuidFromVolume calls dbf.findByUuid(volumeUuid, VolumeVO.class) and then immediately dereferences vol.getVmInstanceUuid(), which can NPE if the VolumeVO is not found; update getHostUuidFromVolume to null-check the result of dbf.findByUuid (the local variable vol) and handle the missing-volume case early (e.g. return the existing empty hostUuid or otherwise propagate a clear failure) before any call to vol.getVmInstanceUuid(); ensure all references in getHostUuidFromVolume (including use of connectedHost and connectedHostUuid) remain safe when vol is null so the caller (e.g. code invoked by UpdateVmInstanceMetadataOnPrimaryStorageMsg) receives a controlled response instead of an NPE.
1953-1954:⚠️ Potential issue | 🟠 Major这些分支应该按
hostUuid选 backend。这里已经拿到了明确的
hostUuid,继续调用getUsableBackend()仍可能在同一 NFS 挂到多种 hypervisor cluster 时选到别的 backend,最终把请求派发到错误实现。这里应统一改成 Line 716-720 已有的getBackendByHostUuid(hostUuid);UpdateVmInstanceMetadataOnPrimaryStorageMsg分支别忘了在backend == null的错误路径上chain.next()。Also applies to: 1988-1990, 2016-2018, 2044-2046, 2072-2074, 2091-2107
🤖 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 1953 - 1954, Replace calls to getUsableBackend() with getBackendByHostUuid(hostUuid) so the selected NfsPrimaryStorageBackend is chosen based on the known hostUuid; update each location where backend is fetched (the spots using NfsPrimaryStorageBackend backend = getUsableBackend() followed by backend.handle(...)) to use getBackendByHostUuid(hostUuid) instead. In the UpdateVmInstanceMetadataOnPrimaryStorageMsg handling branch, add the null-check path so that if getBackendByHostUuid(hostUuid) returns null you call chain.next() (and avoid invoking backend.handle on null). Ensure all listed occurrences (including the one at the snippet and the other similar ranges) use getBackendByHostUuid(hostUuid) and include the backend == null -> chain.next() error path.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
927-929:⚠️ Potential issue | 🔴 Critical删除这两个子类里重复声明的
storagePath字段。
AgentCommand已在 Line 94 定义了storagePath。这里再次声明后,Line 3921/3944 写的是子类字段,而httpCall()写的是父类字段,Get/Scan命令对象会同时带着两个同名成员,序列化结果不再可靠。建议修改
public static class GetVmInstanceMetadataCmd extends AgentCommand { - public String storagePath; public String vmInstanceUuid; } @@ public static class ScanVmMetadataCmd extends AgentCommand { - public String storagePath; public List<String> vmUuids; }As per coding guidelines, "避免在父子类或同一代码块中出现相同名字的成员或局部变量,防止混淆。"
Also applies to: 947-949
🤖 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 927 - 929, Remove the duplicate storagePath field declarations from the subclass command classes so they rely on the storagePath defined in the parent AgentCommand; specifically, delete the storagePath member from GetVmInstanceMetadataCmd and the sibling command class that also declares storagePath (the class declared a few lines later), leaving all code (including httpCall(), Get/Scan command serialization) to use the single storagePath on AgentCommand to avoid dual-member serialization conflicts.
🧹 Nitpick comments (5)
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java (2)
84-91: 考虑对 UUID 为空的 snapshotGroup 记录警告或抛出异常。当前实现中,如果
snapshotGroup的uuid字段为null,会被静默跳过。这可能会隐藏元数据完整性问题——一个没有 UUID 的快照组条目很可能是数据损坏的信号。♻️ 建议的改进
Set<String> groupUuids = new HashSet<>(); for (String groupJson : dto.snapshotGroups) { Map<String, Object> groupMap = JSONObjectUtil.toObject(groupJson, Map.class); Object uuid = groupMap.get("uuid"); - if (uuid != null) { - groupUuids.add(uuid.toString()); + if (uuid == null) { + logger.warn("metadata snapshotGroup entry is missing uuid field"); + continue; } + groupUuids.add(uuid.toString()); }🤖 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/VmInstanceMetadataValidator.java` around lines 84 - 91, In VmInstanceMetadataValidator (processing dto.snapshotGroups in the loop over groupJson), add a check for a null/missing uuid on each parsed groupMap: if uuid is null, either log a clear warning (include the offending groupJson and vm/metadata identifiers) or throw an IllegalArgumentException to fail fast and surface the data integrity issue; ensure you reference the variables groupJson, groupMap and the dto context in the log/exception message so the problematic snapshotGroup can be traced.
52-73: 建议添加 JSON 解析的异常处理以提供更清晰的错误上下文。当
rm.vo包含格式错误的 JSON 时,JSONObjectUtil.toObject()抛出的异常不会包含path信息,导致难以定位是哪个资源(vm、volumes[i] 或 nics[i])的元数据损坏。♻️ 建议的改进
private static void validateSingleResourceUuid(ResourceMetadata rm, String path) { if (rm.resourceUuid == null) { throw new CloudRuntimeException(String.format( "metadata %s.resourceUuid is null", path)); } if (rm.vo == null) { throw new CloudRuntimeException(String.format( "metadata %s.vo is null", path)); } - Map<String, Object> voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + Map<String, Object> voMap; + try { + voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata %s.vo is not valid JSON: %s", path, e.getMessage()), e); + } Object voUuid = voMap.get("uuid");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java` around lines 52 - 73, validateSingleResourceUuid currently calls JSONObjectUtil.toObject(rm.vo, Map.class) with no JSON parse error handling; wrap that call in a try-catch and on any exception rethrow a CloudRuntimeException that includes the path, the resource identifier (rm.resourceUuid or rm.vo), and the original exception (as cause) so callers can see which metadata (vm, volumes[i], nics[i]) failed; specifically modify validateSingleResourceUuid to try { Map<String,Object> voMap = JSONObjectUtil.toObject(...); } catch (Exception e) { throw new CloudRuntimeException(String.format("metadata %s.vo is not valid JSON for resourceUuid=%s: %s", path, rm.resourceUuid, rm.vo), e); } and keep the subsequent voMap/uuid checks unchanged.header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java (1)
27-47: 实体结构清晰,但缺少审计字段相比
VmMetadataDirtyVO(包含createDate、lastOpDate和@PreUpdate方法),此实体缺少标准的审计字段。如果这是有意设计(因为
lastFlushTime已能追踪更新时间),则无需修改。但如果需要追踪记录的创建时间,建议添加createDate字段以保持与其他元数据 VO 的一致性。♻️ 可选:添加审计字段以保持一致性
`@Column` `@Lob` private String metadataSnapshot; + + `@Column` + private Timestamp createDate; + + `@Column` + private Timestamp lastOpDate; + + `@PreUpdate` + private void preUpdate() { + lastOpDate = null; + }🤖 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/VmMetadataFingerprintVO.java` around lines 27 - 47, VmMetadataFingerprintVO currently lacks standard audit fields present in VmMetadataDirtyVO; add a createDate (Timestamp) field, a lastOpDate (Timestamp) field, and a `@PreUpdate` method to set lastOpDate before updates so the entity maintains consistent creation and last-operation timestamps; update the class VmMetadataFingerprintVO to declare the new fields (with `@Column`) and implement the `@PreUpdate` lifecycle method to set lastOpDate, mirroring the audit pattern used in VmMetadataDirtyVO.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (2)
2061-2061: 缺少@Override注解。
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg, ...)方法缺少@Override注解,而本文件中其他同签名的handle方法(如 lines 2097, 2128, 2168 等)都有该注解。建议补全以保持一致性。♻️ 建议修复
+ `@Override` public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) {🤖 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` at line 2061, Method handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg, String, ReturnValueCompletion) in class NfsPrimaryStorageKVMBackend is missing the `@Override` annotation; add `@Override` immediately above the method declaration for handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) to match the other handle overrides (e.g., the ones at lines 2097, 2128, 2168) and keep consistency.
2220-2250:extractFieldsFromMetadataJson方法未被使用。该方法在项目中没有被任何代码调用,是死代码。如果这是为后续功能预留的代码,建议添加注释说明其用途;否则建议移除此方法。
🤖 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 2220 - 2250, The method extractFieldsFromMetadataJson in class NfsPrimaryStorageKVMBackend is dead code (never called); either remove it or mark it clearly for future use: if you intend to keep it for upcoming features, add a Javadoc/TODO explaining purpose and expected caller(s) and annotate with `@SuppressWarnings`("unused") if appropriate; otherwise delete extractFieldsFromMetadataJson and any related private helpers to avoid confusion and update unit tests or callers if you create a new public API that will use it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java`:
- Around line 140-141: VmGlobalConfig defines VM_METADATA with key "vm.metadata"
which conflicts with the canonical key used in VmInstanceMetadataConstants
(GLOBAL_CONFIG_METADATA_ENABLED); update the GlobalConfig instantiation in class
VmGlobalConfig so the key string matches
VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED (e.g. change
"vm.metadata" to the exact constant value, such as "vm.metadata.enabled"),
keeping the `@GlobalConfigValidation`(validValues = {"true","false"}) intact so
reads/writes are on the same config item and the toggle state stays consistent
across both classes.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java`:
- Around line 2928-2935: The method handle(APIUpdateVmInstanceMetadataMsg msg)
currently marks VMs dirty only when vmMetadataDirtyMarker != null but always
publishes a success event via bus.publish(event); change this so that if
vmMetadataDirtyMarker is null you do not publish the success
APIUpdateVmInstanceMetadataEvent — instead construct and publish a failure event
(or set an error on APIUpdateVmInstanceMetadataEvent) indicating the capability
is missing, and only publish the success event after marking at least one vm
dirty; touch vmMetadataDirtyMarker, APIUpdateVmInstanceMetadataEvent (event) and
bus.publish(...) to implement the check and failure publishing.
- Around line 2878-2923: 当前对每个 vmUuid 在 VmInstanceManagerImpl 中查找 root 盘主存储时如果
psUuid == null 使用了 continue,导致这些 VM 被静默丢弃且不计入成功/失败统计;请修改生成 msgs 的逻辑:在循环中如果
psUuid 为 null 则不要 continue,而是记录该 vmUuid 到一个 missedPrimaryStorage 或 failedVmUuids
临时列表(或直接增加一个 missedCount),以便后续在返回事件时将这些 vm 标记为失败并计入 totalFailed;保留对
CleanupVmInstanceMetadataOnPrimaryStorageMsg 列表 msgs 的构建逻辑不变,但在处理 msgs.isEmpty()
分支和最终 bus.send 回调时合并这些因找不到主存储的 vmUuid(唯一标识符 vmUuid)到 evt.setFailedVmUuids(...)
并调整 evt.setTotalFailed(...) 的值(比如 totalFailed += missedCount)。
In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java`:
- Around line 33-34: VmInstanceState 中新增的枚举常量 Registering 缺少在静态初始化块中的
transactions(...) 定义,导致调用 nextState() 时 transactionMap 为空并抛出异常;请在静态初始化块(紧接在
Crashed.transactions(...) 之后)为 Registering 添加与其他 offline 状态(如
Created、Stopped)类似的转换定义,至少包含对 ChangeVmMetaDataMsg、APIDestroyVmInstanceMsg 和
DestroyVmInstanceMsg 的转换路径(例如到 destroying 和 destroyed 事件),确保
Registering.transactions(...) 将这些事件映射到正确的目标状态以避免运行时异常。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3453-3486: The current
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) only picks
connectedHostUuids.get(0), causing scan/cleanup to run against a single host;
change it to iterate over all connectedHostUuids, call the hypervisor backend
via
getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self).handle(msg,
hostUuid, ...) for each host, aggregate successful
ScanVmInstanceMetadataFromPrimaryStorageReply results and merge errors (or fail
only if all hosts fail), and only then bus.reply(msg, aggregatedReply); for
cleanup paths (the same code area using connectedHostUuids and
LocalStorageHypervisorBackend.handle) either determine the correct host that
owns the metadata before calling or iterate all connected hosts and attempt
cleanup on each, collecting outcomes and reporting an aggregated result. Ensure
concurrent callbacks are synchronized (countdown/AtomicInteger/Completion) so
reply is sent once all host callbacks complete.
- Around line 3418-3451: The handler currently picks an arbitrary connected host
(connectedHostUuids.get(0)) instead of locating the host that actually holds the
VM's metadata; change handle(GetVmInstanceMetadataFromPrimaryStorageMsg) to
first use msg.getVmInstanceUuid() to find the host that contains the VM's root
volume/metadata (query VolumeVO/VolumeInventory for root volume where
vmInstanceUuid = msg.getVmInstanceUuid() and primaryStorageUuid =
self.getUuid(), then join to LocalStorageHostRefVO/HostVO to get the hostUuid
with HostStatus.Connected), and if found use that hostUuid with
getHypervisorBackendFactoryByHostUuid(...) and bkd.handle(...); if no such host
is found return an error (or optionally fall back to the existing connected-host
logic) so requests are routed to the host that actually stores the metadata.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 927-929: Remove the duplicate storagePath field declarations from
the subclass command classes so they rely on the storagePath defined in the
parent AgentCommand; specifically, delete the storagePath member from
GetVmInstanceMetadataCmd and the sibling command class that also declares
storagePath (the class declared a few lines later), leaving all code (including
httpCall(), Get/Scan command serialization) to use the single storagePath on
AgentCommand to avoid dual-member serialization conflicts.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1897-1906: The helper getHostUuidFromVolume calls
dbf.findByUuid(volumeUuid, VolumeVO.class) and then immediately dereferences
vol.getVmInstanceUuid(), which can NPE if the VolumeVO is not found; update
getHostUuidFromVolume to null-check the result of dbf.findByUuid (the local
variable vol) and handle the missing-volume case early (e.g. return the existing
empty hostUuid or otherwise propagate a clear failure) before any call to
vol.getVmInstanceUuid(); ensure all references in getHostUuidFromVolume
(including use of connectedHost and connectedHostUuid) remain safe when vol is
null so the caller (e.g. code invoked by
UpdateVmInstanceMetadataOnPrimaryStorageMsg) receives a controlled response
instead of an NPE.
- Around line 1953-1954: Replace calls to getUsableBackend() with
getBackendByHostUuid(hostUuid) so the selected NfsPrimaryStorageBackend is
chosen based on the known hostUuid; update each location where backend is
fetched (the spots using NfsPrimaryStorageBackend backend = getUsableBackend()
followed by backend.handle(...)) to use getBackendByHostUuid(hostUuid) instead.
In the UpdateVmInstanceMetadataOnPrimaryStorageMsg handling branch, add the
null-check path so that if getBackendByHostUuid(hostUuid) returns null you call
chain.next() (and avoid invoking backend.handle on null). Ensure all listed
occurrences (including the one at the snippet and the other similar ranges) use
getBackendByHostUuid(hostUuid) and include the backend == null -> chain.next()
error path.
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java`:
- Around line 84-91: In VmInstanceMetadataValidator (processing
dto.snapshotGroups in the loop over groupJson), add a check for a null/missing
uuid on each parsed groupMap: if uuid is null, either log a clear warning
(include the offending groupJson and vm/metadata identifiers) or throw an
IllegalArgumentException to fail fast and surface the data integrity issue;
ensure you reference the variables groupJson, groupMap and the dto context in
the log/exception message so the problematic snapshotGroup can be traced.
- Around line 52-73: validateSingleResourceUuid currently calls
JSONObjectUtil.toObject(rm.vo, Map.class) with no JSON parse error handling;
wrap that call in a try-catch and on any exception rethrow a
CloudRuntimeException that includes the path, the resource identifier
(rm.resourceUuid or rm.vo), and the original exception (as cause) so callers can
see which metadata (vm, volumes[i], nics[i]) failed; specifically modify
validateSingleResourceUuid to try { Map<String,Object> voMap =
JSONObjectUtil.toObject(...); } catch (Exception e) { throw new
CloudRuntimeException(String.format("metadata %s.vo is not valid JSON for
resourceUuid=%s: %s", path, rm.resourceUuid, rm.vo), e); } and keep the
subsequent voMap/uuid checks unchanged.
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java`:
- Around line 27-47: VmMetadataFingerprintVO currently lacks standard audit
fields present in VmMetadataDirtyVO; add a createDate (Timestamp) field, a
lastOpDate (Timestamp) field, and a `@PreUpdate` method to set lastOpDate before
updates so the entity maintains consistent creation and last-operation
timestamps; update the class VmMetadataFingerprintVO to declare the new fields
(with `@Column`) and implement the `@PreUpdate` lifecycle method to set lastOpDate,
mirroring the audit pattern used in VmMetadataDirtyVO.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Line 2061: Method handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg, String,
ReturnValueCompletion) in class NfsPrimaryStorageKVMBackend is missing the
`@Override` annotation; add `@Override` immediately above the method declaration for
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion)
to match the other handle overrides (e.g., the ones at lines 2097, 2128, 2168)
and keep consistency.
- Around line 2220-2250: The method extractFieldsFromMetadataJson in class
NfsPrimaryStorageKVMBackend is dead code (never called); either remove it or
mark it clearly for future use: if you intend to keep it for upcoming features,
add a Javadoc/TODO explaining purpose and expected caller(s) and annotate with
`@SuppressWarnings`("unused") if appropriate; otherwise delete
extractFieldsFromMetadataJson and any related private helpers to avoid confusion
and update unit tests or callers if you create a new public API that will use
it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6813bfbc-f7a5-4b02-86a6-5a19609326cd
⛔ 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 (95)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.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/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/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/ReadVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.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/VmInstanceMetadataSummary.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/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/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/VmInstanceMetadataRegistrationSpec.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.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/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/APICreateVolumeSnapshotGroupMsg.javaplugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.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/LocalStorageKvmMigrateVmFlow.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/LocalStorageVmMetadataPathReplacementExtension.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.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/NfsVmMetadataPathReplacementExtension.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/VmInstanceMetadataSummary.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.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 with no reviewable changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
✅ Files skipped from review due to trivial changes (53)
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/VmInstanceMetadataSummary.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/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java
- sdk/src/main/java/org/zstack/sdk/VmInstanceMetadataSummary.java
- simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.java
🚧 Files skipped from review as they are similar to previous changes (23)
- compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
- compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmMigrateVmFlow.java
- header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataPathReplacementExtension.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataPathReplacementExtension.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
2ef5d1f to
60a690e
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (7)
compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java (1)
140-141:⚠️ Potential issue | 🟠 MajorVM 元数据总开关 key 仍与现有常量不一致。
VM_METADATA仍注册为"vm.metadata",会与已有"vm.metadata.enabled"形成两个独立配置项,导致读写不一致。建议修复
- public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata"); + public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata.enabled");🤖 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 140 - 141, The VM_METADATA GlobalConfig is registered with the wrong key ("vm.metadata") causing a duplicate with the existing "vm.metadata.enabled" setting; update the key in the VM_METADATA declaration (the GlobalConfig instantiation in VmGlobalConfig) to "vm.metadata.enabled" so the constant VM_METADATA refers to the same configuration item and avoids separate read/write entries.compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java (2)
2928-2935:⚠️ Potential issue | 🟠 Major能力缺失时仍返回成功事件,语义不正确。
当
vmMetadataDirtyMarker不可用时,当前逻辑依旧bus.publish(event)成功返回,会掩盖“未实际执行标脏”的失败。建议修复
private void handle(APIUpdateVmInstanceMetadataMsg msg) { APIUpdateVmInstanceMetadataEvent event = new APIUpdateVmInstanceMetadataEvent(msg.getId()); - if (vmMetadataDirtyMarker != null) { - for (String vmUuid : msg.getVmUuids()) { - vmMetadataDirtyMarker.markDirty(vmUuid, true); - } + if (vmMetadataDirtyMarker == null) { + event.setError(operr("vm metadata dirty service is unavailable")); + bus.publish(event); + return; } + + for (String vmUuid : msg.getVmUuids()) { + vmMetadataDirtyMarker.markDirty(vmUuid, true); + } bus.publish(event); }🤖 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/VmInstanceManagerImpl.java` around lines 2928 - 2935, The handler handle(APIUpdateVmInstanceMetadataMsg msg) currently always publishes a success APIUpdateVmInstanceMetadataEvent even when vmMetadataDirtyMarker is null; change the logic so that if vmMetadataDirtyMarker is null you publish a failure APIUpdateVmInstanceMetadataEvent (populate an appropriate error/failure message or set success=false) instead of the success event, and only publish the success event after you have marked all vmUuid entries via vmMetadataDirtyMarker.markDirty(vmUuid, true); reference the vmMetadataDirtyMarker field, the vmUuid loop and the APIUpdateVmInstanceMetadataEvent/bus.publish calls when making the change.
2878-2923:⚠️ Potential issue | 🟠 Major清理统计仍会漏记无法定位主存储的 VM。
Line 2886-2887 直接
continue,导致这部分 VM 既不计成功也不计失败,返回统计会失真。建议修复
- List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); + List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); + List<String> unresolvedVmUuids = new ArrayList<>(); for (String vmUuid : vmUuids) { String psUuid = Q.New(VolumeVO.class) .eq(VolumeVO_.vmInstanceUuid, vmUuid) .eq(VolumeVO_.type, VolumeType.Root) .select(VolumeVO_.primaryStorageUuid) .findValue(); if (psUuid == null) { + unresolvedVmUuids.add(vmUuid); continue; } @@ if (msgs.isEmpty()) { evt.setTotalCleaned(0); - evt.setTotalFailed(0); - evt.setFailedVmUuids(new ArrayList<>()); + evt.setTotalFailed(unresolvedVmUuids.size()); + evt.setFailedVmUuids(unresolvedVmUuids); bus.publish(evt); return; } @@ - int totalFailed = 0; - List<String> failedVmUuids = new ArrayList<>(); + int totalFailed = unresolvedVmUuids.size(); + List<String> failedVmUuids = new ArrayList<>(unresolvedVmUuids);🤖 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/VmInstanceManagerImpl.java` around lines 2878 - 2923, The loop over vmUuids skips VMs with null psUuid (obtained via Q.New(VolumeVO.class)...select(VolumeVO_.primaryStorageUuid)) which causes those VMs to be omitted from the final statistics; modify the logic in the vmUuids processing so that when psUuid is null you increment totalFailed (or add to a failedVmUuids collection) and include that in evt.setTotalFailed / evt.setFailedVmUuids; keep building msgs for VMs that do have psUuid and after sending replies merge the pre-collected failures with failures from CloudBusListCallBack before publishing evt (update evt.setTotalCleaned, evt.setTotalFailed and evt.setFailedVmUuids accordingly).plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (3)
3494-3512:⚠️ Potential issue | 🔴 Criticalcleanup 也不能清第一台在线 host。
agent 端的
CleanupVmInstanceMetadataOnPrimaryStorageMsg会在指定 host 上删除self.getUrl()/.../<vmUuid>.json。这里随机选第一台 connected host,经常会打到不持有该 VM metadata 的主机,导致清理失效或表现不稳定。请先定位 metadata 所属 host,或至少遍历全部 connected hosts 后再汇总结果。🤖 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 3494 - 3512, The code currently picks the first host from connectedHostUuids and calls f.getHypervisorBackend(self).handle(...) which can miss the host that actually stores the VM metadata; change handle logic in LocalStorageBase (around connectedHostUuids usage) to either (1) query/resolve which host actually owns the VM metadata and call getHypervisorBackendFactoryByHostUuid(hostUuid) / LocalStorageHypervisorBackend.handle(...) for that host only, or (2) iterate all connectedHostUuids, invoke LocalStorageHypervisorBackend.handle(...) for each host, collect and combine their ReturnValueCompletion results, and only reply success when the aggregated responses indicate the metadata was removed (or fail if none succeeded). Ensure you use the existing symbols connectedHostUuids, getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorBackend.handle and the CleanupVmInstanceMetadataOnPrimaryStorageReply completion to aggregate outcomes.
3422-3438:⚠️ Potential issue | 🔴 Critical不要随机挑一台在线 host 读取单 VM metadata。
plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java的handle(GetVmInstanceMetadataFromPrimaryStorageMsg, ...)只会在传入的那台 host 上按vmInstanceUuid查找。这里直接取connectedHostUuids.get(0),多 host 场景下会把请求打到错误主机,返回空 metadata 或结果不稳定。请先根据msg.getVmInstanceUuid()/ 根卷资源引用定位承载该 VM metadata 的 host,再下发到对应 host。🤖 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 3422 - 3438, The code randomly picks connectedHostUuids.get(0) and may send GetVmInstanceMetadataFromPrimaryStorageMsg to the wrong host; instead resolve which host actually carries the VM metadata using the vm UUID or the root volume's primary-storage resource reference and target that host. Change the logic in LocalStorageBase where connectedHostUuids is used: given msg.getVmInstanceUuid() (and/or lookup the VM's root volume / LocalStorage resource ref), query the mapping that ties the VM/root-volume to a specific host (the host that hosts the root volume or LocalStorageHostRef for that resource) and use that hostUuid when calling getHypervisorBackendFactoryByHostUuid(...) and f.getHypervisorBackend(self); ensure this path is used for the GetVmInstanceMetadataFromPrimaryStorageMsg handling that delegates to LocalStorageKvmBackend.handle(...) so the request is sent to the correct host instead of connectedHostUuids.get(0).
3457-3474:⚠️ Potential issue | 🔴 Criticalscan 不能只扫第一台在线 host。
LocalStorageKvmBackend.handle(ScanVmInstanceMetadataFromPrimaryStorageMsg, ...)只扫描指定 host 的storagePath。当前实现固定connectedHostUuids.get(0),会天然漏掉其他 host 上的 metadata,而且这里没有order by,命中的 host 还会随数据库返回顺序漂移。应当 fan-out 到全部 connected hosts,并聚合vmInstanceMetadata后再统一回复。🤖 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 3457 - 3474, Currently the code picks only connectedHostUuids.get(0) and calls LocalStorageHypervisorBackend.handle(...) for that single host, which misses metadata on other hosts and relies on unspecified DB order; change the logic to iterate over all connectedHostUuids, call getHypervisorBackendByHostUuid(hostUuid) and bkd.handle(ScanVmInstanceMetadataFromPrimaryStorageMsg, hostUuid, ...) for each host, collect/aggregate vmInstanceMetadata results from all ReturnValueCompletion callbacks (merging lists/maps and deduplicating by unique keys), and only after all callbacks complete send the single aggregated reply via bus.reply(msg, reply); ensure failures from individual hosts are captured and surfaced appropriately in the final reply.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1794-1817:⚠️ Potential issue | 🟠 Major基类默认回成功会把“不支持”伪装成“成功”。
这 5 个默认 handler 现在都直接
bus.reply(...)成功。配合下面的handle(APIScanVmInstanceMetadataFromPrimaryStorageMsg)/handle(APIGetVmInstanceMetadataFromPrimaryStorageMsg),任何没有 override 的 primary storage 类型都会对外表现为“支持 metadata”,但实际上什么都没做。这里应 fail-fast,至少返回明确的 unsupported/override-required 错误。🤖 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 1794 - 1817, 这些默认 handler(PrimaryStorageBase 的 handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg)、handle(ReadVmInstanceMetadataOnPrimaryStorageMsg)、handle(GetVmInstanceMetadataFromPrimaryStorageMsg)、handle(ScanVmInstanceMetadataFromPrimaryStorageMsg) 和 handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg))当前直接返回成功并将“不支持”伪装成成功;请改为 fail-fast:不要直接 bus.reply 成功,而是在各自 reply 对象上设置明确错误(例如 reply.setSuccess(false) 或调用 setError()/setException() 并填写“unsupported/override-required”或等价信息),然后将该失败 reply 通过 bus.reply 返回,要求子类必须 override 以提供实际实现(让 APIScanVmInstanceMetadataFromPrimaryStorageMsg / APIGetVmInstanceMetadataFromPrimaryStorageMsg 不再误判为支持)。
🧹 Nitpick comments (4)
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java (2)
62-62: 建议包装 JSON 解析异常
JSONObjectUtil.toObject()在 JSON 格式错误时可能抛出非预期的异常类型。建议捕获解析异常并包装为CloudRuntimeException,提供更清晰的错误信息。♻️ 建议的改进
- Map<String, Object> voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + Map<String, Object> voMap; + try { + voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata %s.vo is not valid JSON: %s", path, e.getMessage()), e); + }🤖 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/VmInstanceMetadataValidator.java` at line 62, 在 VmInstanceMetadataValidator 中调用 JSONObjectUtil.toObject(rm.vo, Map.class) 可能抛出 JSON 解析异常;请在该调用处捕获可能的解析异常(例如 JSONException 或其他运行时异常)并将其包装为 CloudRuntimeException,包含说明性的消息(例如 "failed to parse vm metadata")和原始异常作为 cause,同时保持返回值赋给 voMap 不变;定位到使用 JSONObjectUtil.toObject 的行并实现 try-catch 来抛出新的 CloudRuntimeException。
84-100: 同样建议为 JSON 解析添加异常处理
snapshotGroups和snapshotGroupRefs的 JSON 解析(第 86、94 行)同样缺少异常处理。建议统一添加 try-catch 包装,确保解析失败时抛出明确的CloudRuntimeException。♻️ 建议的改进
Set<String> groupUuids = new HashSet<>(); for (String groupJson : dto.snapshotGroups) { - Map<String, Object> groupMap = JSONObjectUtil.toObject(groupJson, Map.class); + Map<String, Object> groupMap; + try { + groupMap = JSONObjectUtil.toObject(groupJson, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata snapshotGroup is not valid JSON: %s", e.getMessage()), e); + } Object uuid = groupMap.get("uuid"); if (uuid != null) { groupUuids.add(uuid.toString()); } } for (String refJson : dto.snapshotGroupRefs) { - Map<String, Object> refMap = JSONObjectUtil.toObject(refJson, Map.class); + Map<String, Object> refMap; + try { + refMap = JSONObjectUtil.toObject(refJson, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata snapshotGroupRef is not valid JSON: %s", e.getMessage()), e); + } Object groupUuid = refMap.get("volumeSnapshotGroupUuid");🤖 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/VmInstanceMetadataValidator.java` around lines 84 - 100, Wrap the JSON parsing calls in VmInstanceMetadataValidator where dto.snapshotGroups and dto.snapshotGroupRefs are converted (the JSONObjectUtil.toObject calls for groupJson and refJson) with try-catch blocks; on any parsing exception catch it and rethrow a CloudRuntimeException with a clear message including the offending JSON (or its index) and the original exception message so failures are explicit. Ensure you handle both the group parsing loop that builds groupUuids and the ref parsing loop that validates volumeSnapshotGroupUuid, reusing the same error handling pattern around JSONObjectUtil.toObject.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
3976-4006: 删除未使用的私有方法extractFieldsFromMetadataJson。该方法定义在 3976-4006 行,但在整个代码库中未被调用,属于死代码。若为将来功能预留,建议添加注释说明意图;否则应删除以保持代码整洁。
🤖 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 3976 - 4006, Remove the dead private method extractFieldsFromMetadataJson from class LocalStorageKvmBackend since it is not referenced anywhere; delete the entire method declaration and body, and clean up any now-unused imports related only to that method (e.g., com.google.gson.* usages) or add a brief TODO comment if you intend to keep a placeholder for future use instead of deleting it.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2220-2250: 未使用的私有方法。
extractFieldsFromMetadataJson方法在当前代码中未被调用。根据 AI 摘要,该方法"not used by the new handlers"。ScanVmInstanceMetadataFromPrimaryStorageMsg处理器(Lines 2199-2213)直接从VmMetadataScanEntry映射字段,而非解析 JSON 元数据。如果该方法是为将来功能预留,建议添加注释说明;否则应考虑移除以避免死代码。
🤖 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 2220 - 2250, The private method extractFieldsFromMetadataJson is currently unused (handlers like ScanVmInstanceMetadataFromPrimaryStorageMsg map from VmMetadataScanEntry instead), so either remove the dead method or make its intent explicit: if it's reserved for future use, add a clear JavaDoc comment above extractFieldsFromMetadataJson stating it's intentionally retained for future JSON-parsing use and reference the handler names (e.g., ScanVmInstanceMetadataFromPrimaryStorageMsg, VmMetadataScanEntry); otherwise delete extractFieldsFromMetadataJson to eliminate dead code and clean up imports. Ensure any callers are updated if you choose to keep it (search for usages of extractFieldsFromMetadataJson) and run tests after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Line 2061: Add the missing `@Override` annotation to the method public void
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion)
in NfsPrimaryStorageKVMBackend so it consistently marks the interface
implementation of NfsPrimaryStorageBackend; place `@Override` directly above that
method declaration (matching the other handle(...) methods such as the ones
handling different message types) to make the intent explicit and maintain
consistency.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataPathReplacementExtension.java`:
- Around line 47-50: The loop in NfsVmMetadataPathReplacementExtension that
iterates over allOldPaths can NPE when allOldPaths is null and extractOldPrefix
uses idx > 0 which skips valid markers at index 0; update the code in the method
that contains the for-loop (and the helper extractOldPrefix) to first null-check
allOldPaths before iterating (return or continue when null/empty), and change
the marker-position check from idx > 0 to idx >= 0 (or otherwise allow idx == 0)
so prefixes starting at the first character (e.g., "/rootVolumes/...") are
accepted; ensure extractOldPrefix returns null only for truly missing markers
and the outer loop breaks once a non-null oldPrefix is found.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java`:
- Around line 140-141: The VM_METADATA GlobalConfig is registered with the wrong
key ("vm.metadata") causing a duplicate with the existing "vm.metadata.enabled"
setting; update the key in the VM_METADATA declaration (the GlobalConfig
instantiation in VmGlobalConfig) to "vm.metadata.enabled" so the constant
VM_METADATA refers to the same configuration item and avoids separate read/write
entries.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java`:
- Around line 2928-2935: The handler handle(APIUpdateVmInstanceMetadataMsg msg)
currently always publishes a success APIUpdateVmInstanceMetadataEvent even when
vmMetadataDirtyMarker is null; change the logic so that if vmMetadataDirtyMarker
is null you publish a failure APIUpdateVmInstanceMetadataEvent (populate an
appropriate error/failure message or set success=false) instead of the success
event, and only publish the success event after you have marked all vmUuid
entries via vmMetadataDirtyMarker.markDirty(vmUuid, true); reference the
vmMetadataDirtyMarker field, the vmUuid loop and the
APIUpdateVmInstanceMetadataEvent/bus.publish calls when making the change.
- Around line 2878-2923: The loop over vmUuids skips VMs with null psUuid
(obtained via Q.New(VolumeVO.class)...select(VolumeVO_.primaryStorageUuid))
which causes those VMs to be omitted from the final statistics; modify the logic
in the vmUuids processing so that when psUuid is null you increment totalFailed
(or add to a failedVmUuids collection) and include that in evt.setTotalFailed /
evt.setFailedVmUuids; keep building msgs for VMs that do have psUuid and after
sending replies merge the pre-collected failures with failures from
CloudBusListCallBack before publishing evt (update evt.setTotalCleaned,
evt.setTotalFailed and evt.setFailedVmUuids accordingly).
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3494-3512: The code currently picks the first host from
connectedHostUuids and calls f.getHypervisorBackend(self).handle(...) which can
miss the host that actually stores the VM metadata; change handle logic in
LocalStorageBase (around connectedHostUuids usage) to either (1) query/resolve
which host actually owns the VM metadata and call
getHypervisorBackendFactoryByHostUuid(hostUuid) /
LocalStorageHypervisorBackend.handle(...) for that host only, or (2) iterate all
connectedHostUuids, invoke LocalStorageHypervisorBackend.handle(...) for each
host, collect and combine their ReturnValueCompletion results, and only reply
success when the aggregated responses indicate the metadata was removed (or fail
if none succeeded). Ensure you use the existing symbols connectedHostUuids,
getHypervisorBackendFactoryByHostUuid, LocalStorageHypervisorBackend.handle and
the CleanupVmInstanceMetadataOnPrimaryStorageReply completion to aggregate
outcomes.
- Around line 3422-3438: The code randomly picks connectedHostUuids.get(0) and
may send GetVmInstanceMetadataFromPrimaryStorageMsg to the wrong host; instead
resolve which host actually carries the VM metadata using the vm UUID or the
root volume's primary-storage resource reference and target that host. Change
the logic in LocalStorageBase where connectedHostUuids is used: given
msg.getVmInstanceUuid() (and/or lookup the VM's root volume / LocalStorage
resource ref), query the mapping that ties the VM/root-volume to a specific host
(the host that hosts the root volume or LocalStorageHostRef for that resource)
and use that hostUuid when calling getHypervisorBackendFactoryByHostUuid(...)
and f.getHypervisorBackend(self); ensure this path is used for the
GetVmInstanceMetadataFromPrimaryStorageMsg handling that delegates to
LocalStorageKvmBackend.handle(...) so the request is sent to the correct host
instead of connectedHostUuids.get(0).
- Around line 3457-3474: Currently the code picks only connectedHostUuids.get(0)
and calls LocalStorageHypervisorBackend.handle(...) for that single host, which
misses metadata on other hosts and relies on unspecified DB order; change the
logic to iterate over all connectedHostUuids, call
getHypervisorBackendByHostUuid(hostUuid) and
bkd.handle(ScanVmInstanceMetadataFromPrimaryStorageMsg, hostUuid, ...) for each
host, collect/aggregate vmInstanceMetadata results from all
ReturnValueCompletion callbacks (merging lists/maps and deduplicating by unique
keys), and only after all callbacks complete send the single aggregated reply
via bus.reply(msg, reply); ensure failures from individual hosts are captured
and surfaced appropriately in the final reply.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1794-1817: 这些默认 handler(PrimaryStorageBase 的
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg)、handle(ReadVmInstanceMetadataOnPrimaryStorageMsg)、handle(GetVmInstanceMetadataFromPrimaryStorageMsg)、handle(ScanVmInstanceMetadataFromPrimaryStorageMsg)
和 handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg))当前直接返回成功并将“不支持”伪装成成功;请改为
fail-fast:不要直接 bus.reply 成功,而是在各自 reply 对象上设置明确错误(例如 reply.setSuccess(false) 或调用
setError()/setException() 并填写“unsupported/override-required”或等价信息),然后将该失败 reply
通过 bus.reply 返回,要求子类必须 override 以提供实际实现(让
APIScanVmInstanceMetadataFromPrimaryStorageMsg /
APIGetVmInstanceMetadataFromPrimaryStorageMsg 不再误判为支持)。
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java`:
- Line 62: 在 VmInstanceMetadataValidator 中调用 JSONObjectUtil.toObject(rm.vo,
Map.class) 可能抛出 JSON 解析异常;请在该调用处捕获可能的解析异常(例如 JSONException 或其他运行时异常)并将其包装为
CloudRuntimeException,包含说明性的消息(例如 "failed to parse vm metadata")和原始异常作为
cause,同时保持返回值赋给 voMap 不变;定位到使用 JSONObjectUtil.toObject 的行并实现 try-catch 来抛出新的
CloudRuntimeException。
- Around line 84-100: Wrap the JSON parsing calls in VmInstanceMetadataValidator
where dto.snapshotGroups and dto.snapshotGroupRefs are converted (the
JSONObjectUtil.toObject calls for groupJson and refJson) with try-catch blocks;
on any parsing exception catch it and rethrow a CloudRuntimeException with a
clear message including the offending JSON (or its index) and the original
exception message so failures are explicit. Ensure you handle both the group
parsing loop that builds groupUuids and the ref parsing loop that validates
volumeSnapshotGroupUuid, reusing the same error handling pattern around
JSONObjectUtil.toObject.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 3976-4006: Remove the dead private method
extractFieldsFromMetadataJson from class LocalStorageKvmBackend since it is not
referenced anywhere; delete the entire method declaration and body, and clean up
any now-unused imports related only to that method (e.g., com.google.gson.*
usages) or add a brief TODO comment if you intend to keep a placeholder for
future use instead of deleting it.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2220-2250: The private method extractFieldsFromMetadataJson is
currently unused (handlers like ScanVmInstanceMetadataFromPrimaryStorageMsg map
from VmMetadataScanEntry instead), so either remove the dead method or make its
intent explicit: if it's reserved for future use, add a clear JavaDoc comment
above extractFieldsFromMetadataJson stating it's intentionally retained for
future JSON-parsing use and reference the handler names (e.g.,
ScanVmInstanceMetadataFromPrimaryStorageMsg, VmMetadataScanEntry); otherwise
delete extractFieldsFromMetadataJson to eliminate dead code and clean up
imports. Ensure any callers are updated if you choose to keep it (search for
usages of extractFieldsFromMetadataJson) and run tests after the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 65996ce8-e707-4dcb-8de1-80af7877a542
⛔ 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 (94)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.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/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/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/ReadVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.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/VmInstanceMetadataSummary.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/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/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/VmInstanceMetadataRegistrationSpec.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.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/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/APICreateVolumeSnapshotGroupMsg.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/LocalStorageKvmMigrateVmFlow.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/LocalStorageVmMetadataPathReplacementExtension.javaplugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.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/NfsVmMetadataPathReplacementExtension.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/VmInstanceMetadataSummary.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.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 with no reviewable changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
✅ Files skipped from review due to trivial changes (50)
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
🚧 Files skipped from review as they are similar to previous changes (23)
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/VmInstanceState.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataResourcePersistExtension.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- conf/db/upgrade/V5.0.0__schema.sql
- simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java
- compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
| }); | ||
| } | ||
|
|
||
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { |
There was a problem hiding this comment.
缺少 @Override 注解。
该方法实现了 NfsPrimaryStorageBackend 接口中的方法,与后续的 handle 方法(如 Lines 2097、2128、2168、2252、2289)相比,此处缺少 @Override 注解,存在不一致性。
🛠️ 建议修复
+ `@Override`
public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { | |
| `@Override` | |
| public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion) { |
🤖 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`
at line 2061, Add the missing `@Override` annotation to the method public void
handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid,
ReturnValueCompletion<UpdateVmInstanceMetadataOnPrimaryStorageReply> completion)
in NfsPrimaryStorageKVMBackend so it consistently marks the interface
implementation of NfsPrimaryStorageBackend; place `@Override` directly above that
method declaration (matching the other handle(...) methods such as the ones
handling different message types) to make the intent explicit and maintain
consistency.
| for (String path : allOldPaths) { | ||
| oldPrefix = extractOldPrefix(path); | ||
| if (oldPrefix != null) break; | ||
| } |
There was a problem hiding this comment.
路径替换存在两个边界缺陷(NPE + 前缀提取漏判)。
- Line 47:
allOldPaths为null时会直接抛 NPE。 - Line 78:
idx > 0会漏掉 marker 在首位的合法路径(如/rootVolumes/...),导致无法提取oldPrefix。
建议修复
public PathReplacementResult calculatePathReplacements(String targetPsUuid, List<String> allOldPaths) {
@@
String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/";
+
+ if (allOldPaths == null || allOldPaths.isEmpty()) {
+ PathReplacementResult result = new PathReplacementResult();
+ result.setPathMap(Collections.emptyMap());
+ result.setNewPrefix(newPrefix);
+ return result;
+ }
@@
private String extractOldPrefix(String path) {
if (path == null || !path.startsWith("/")) {
return null;
}
String[] markers = {"/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/"};
for (String marker : markers) {
int idx = path.indexOf(marker);
- if (idx > 0) {
+ if (idx >= 0) {
return path.substring(0, idx + 1);
}
}
return null;
}Also applies to: 77-79
🤖 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/NfsVmMetadataPathReplacementExtension.java`
around lines 47 - 50, The loop in NfsVmMetadataPathReplacementExtension that
iterates over allOldPaths can NPE when allOldPaths is null and extractOldPrefix
uses idx > 0 which skips valid markers at index 0; update the code in the method
that contains the for-loop (and the helper extractOldPrefix) to first null-check
allOldPaths before iterating (return or continue when null/empty), and change
the marker-position check from idx > 0 to idx >= 0 (or otherwise allow idx == 0)
so prefixes starting at the first character (e.g., "/rootVolumes/...") are
accepted; ensure extractOldPrefix returns null only for truly missing markers
and the outer loop breaks once a non-null oldPrefix is found.
60a690e to
eecfc92
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (13)
header/src/main/java/org/zstack/header/vm/VmInstanceState.java (1)
33-56:⚠️ Potential issue | 🔴 Critical请为
Registering补齐状态迁移定义,避免状态机运行时异常。Line 34 和 Line 56 引入了新状态,但静态初始化中缺少
Registering.transactions(...)。当流程调用nextState(...)时会在 Line 238 抛出CloudRuntimeException。这是功能性阻断问题。🛠️ 建议补丁
@@ Crashed.transactions( new Transaction(VmInstanceStateEvent.running, VmInstanceState.Running), new Transaction(VmInstanceStateEvent.unknown, VmInstanceState.Unknown), new Transaction(VmInstanceStateEvent.stopping, VmInstanceState.Stopping), new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped), new Transaction(VmInstanceStateEvent.rebooting, VmInstanceState.Rebooting), new Transaction(VmInstanceStateEvent.crashed, VmInstanceState.Crashed), new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed), new Transaction(VmInstanceStateEvent.noState, VmInstanceState.NoState), new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying) ); + Registering.transactions( + new Transaction(VmInstanceStateEvent.running, VmInstanceState.Running), + new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped), + new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying), + new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed), + new Transaction(VmInstanceStateEvent.noState, VmInstanceState.NoState), + new Transaction(VmInstanceStateEvent.unknown, VmInstanceState.Unknown) + ); NoState.transactions(🤖 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 33 - 56, 在 VmInstanceState 中新增的 Registering 状态缺少事务迁移定义,导致调用 nextState(...) 时抛出 CloudRuntimeException;请为枚举值 Registering 添加相应的 transactions(...) 配置(在 VmInstanceState 枚举内),定义允许的事件到目标状态的映射,类似其他状态(例如 Starting/Stopping)的事务声明,确保 Registering 包含所有应该接受的 VmInstanceStateEvent 到目标 VmInstanceState 的转换,这会修复静态初始化后状态机运行时报错。compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java (1)
140-142:⚠️ Potential issue | 🟠 Major请统一元数据总开关配置 key,避免出现两个独立配置项。
Line 141 当前是
"vm.metadata"。如果其他模块使用"vm.metadata.enabled",会导致开关状态不同步(读取/写入分裂)。🛠️ 建议补丁
+import org.zstack.header.vm.metadata.VmInstanceMetadataConstants; @@ - public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata"); + public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, VmInstanceMetadataConstants.GLOBAL_CONFIG_METADATA_ENABLED);🤖 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 140 - 142, The GlobalConfig key for VM metadata is inconsistent: VmGlobalConfig.VM_METADATA is declared with "vm.metadata" but other modules use "vm.metadata.enabled", causing split reads/writes; change the key in the VmGlobalConfig.VM_METADATA GlobalConfig constructor to the canonical "vm.metadata.enabled" and update any code references that read/write "vm.metadata" (search for literal "vm.metadata" and replace them with "vm.metadata.enabled") so all modules consistently use the same config key.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java (2)
1945-1949:⚠️ Potential issue | 🟠 Major先兜住根卷不存在的场景。
这里新接入了
getHostUuidFromVolume(msg.getRootVolumeUuid()),但同文件里的 helper 在 Line 1898 取到VolumeVO后会直接访问vol.getVmInstanceUuid()。一旦根卷 UUID 已失效,会先抛 NPE,根本走不到这里的错误回复分支。建议先在 helper 内对dbf.findByUuid()的返回值做空判断,再返回空值或明确的业务错误。💡 建议修改
private String getHostUuidFromVolume(String volumeUuid) { VolumeVO vol = dbf.findByUuid(volumeUuid, VolumeVO.class); + if (vol == null) { + return null; + } String hostUuid = ""; List<HostInventory> connectedHost = factory.getConnectedHostForOperation(getSelfInventory());Based on learnings, 在ZStack项目的VolumeSnapshotTreeBase类中,当通过dbf.findByUuid()方法获取VolumeVO对象时,需要进行null检查,因为该方法可能在找不到对应记录时返回null,直接使用可能导致NullPointerException。
🤖 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 1945 - 1949, The helper that calls dbf.findByUuid() (used by getHostUuidFromVolume and located near the VolumeVO handling in VolumeSnapshotTreeBase) must null-check the returned VolumeVO before accessing vol.getVmInstanceUuid(); update that helper to if dbf.findByUuid(...) returns null then return null (or propagate a clear business error) instead of dereferencing, so getHostUuidFromVolume can detect a missing root volume and trigger the existing reply.setError branch; ensure the helper's contract and callers reflect the nullable return or explicit error.
1953-1954:⚠️ Potential issue | 🟠 Major这里应该按
hostUuid选 backend,而不是回退到getUsableBackend()。这些分支里,
hostUuid要么已经从根卷/请求里拿到了,要么刚从connectedHosts里选出来;但后面还是调用getUsableBackend()。NFS 主存储一旦同时挂到不同 hypervisor cluster,这里就可能拿到和hostUuid不匹配的 backend,把请求派发到错误实现。直接复用现成的getBackendByHostUuid(hostUuid)更稳妥,RebaseVolumeBackingFileOnPrimaryStorageMsg那段同理。💡 建议修改
- final NfsPrimaryStorageBackend backend = getUsableBackend(); + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid);Also applies to: 1988-1990, 2016-2018, 2044-2046, 2072-2074, 2091-2107
🤖 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 1953 - 1954, The code chooses a backend via getUsableBackend() even when hostUuid is known, which can dispatch to the wrong backend across clusters; replace calls to getUsableBackend() with getBackendByHostUuid(hostUuid) wherever the hostUuid is available (e.g. in handlers invoking backend.handle(...) for messages like UpdateVmInstanceMetadataOnPrimaryStorageMsg and RebaseVolumeBackingFileOnPrimaryStorageMsg) so the backend is selected by the actual host UUID; ensure you pass the same hostUuid used earlier (from the root volume/request or connectedHosts) into getBackendByHostUuid(hostUuid) and keep the existing ReturnValueCompletion handling intact.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (2)
3457-3474:⚠️ Potential issue | 🔴 Critical
scan/cleanup不能只打一台 host。Local storage 的 metadata 分散在各 host 本地目录上。这里虽然先查了全部 connected host,后面却仍然只用第一台;
scan会天然漏扫,cleanup也会漏删,而且查询没有order by,命中的 host 还不稳定。scan需要 fan-out 到所有相关 host 并聚合结果;cleanup至少要定位到 metadata 所属 host,或者同样遍历所有 host。Also applies to: 3494-3512
🤖 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 3457 - 3474, The code currently queries all connectedHostUuids but only picks the first and calls LocalStorageHypervisorFactory.getHypervisorBackend(...).getHypervisorBackend(self).handle(msg, hostUuid, ...), which causes scan/cleanup to miss metadata on other hosts; change this to fan-out the operation to every host in connectedHostUuids: for each hostUuid obtain the LocalStorageHypervisorFactory and LocalStorageHypervisorBackend and invoke handle(...) concurrently, collect/aggregate all ReturnValueCompletion results (or errors) and only then build and send the final reply for scan (merge metadata lists) and for cleanup (either determine owner host per metadata or run cleanup on every host and merge deletion results); ensure you preserve error aggregation and stable ordering so behavior is deterministic and do not rely on a single host result.
3422-3437:⚠️ Potential issue | 🔴 Critical单 VM metadata 查询不能随机命中第一台在线 host。
Line 3436 直接取
connectedHostUuids.get(0),但 local storage 上的 metadata 是跟根卷一起落在具体 host 上的。多 host 场景下,这会把单 VM 查询发到错误节点,结果要么查不到,要么随数据库返回顺序漂移。这里应该先按请求里的 VM 标识定位 metadata 所属 host,再转发给那台 host。🤖 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 3422 - 3437, The current logic picks the first connected host from connectedHostUuids and calls getHypervisorBackendFactoryByHostUuid(hostUuid), which can route VM metadata queries to the wrong node; instead, use the VM identifier from the incoming request (e.g., msg.getVmInstanceUuid or the request's volume/rootVolume id) to look up which host actually stores that VM's metadata (query the local storage metadata/volume-to-host mapping VO used by LocalStorage, e.g., the VO that maps metadata/root volumes to hostUuid) and set hostUuid to that owner; only if no owner is found, fall back to selecting a connectedHostUuids entry or return the existing error. Ensure you update the code path that currently uses connectedHostUuids.get(0) and then calls getHypervisorBackendFactoryByHostUuid so the forwarded request hits the correct host that holds the metadata.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1794-1817:⚠️ Potential issue | 🟠 Major基类默认实现不能静默成功。
这些
handle(...)已经被新的路由直接调用了。对没有 override 的存储类型来说,Update/Read/Get/Scan/Cleanup现在都会返回 success,但实际上既不写也不读 metadata;Get/Scan还会把空结果继续上抛,调用方无法区分“不支持”和“确实没有数据”。这里更安全的默认行为应当是直接失败。🛠️ 建议修复
protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); - bus.reply(msg, reply); + reply.setError(operr("%s is not implemented by primary storage[type:%s, uuid:%s]", + msg.getClass().getSimpleName(), self.getType(), self.getUuid())); + bus.reply(msg, reply); }对
ReadVmInstanceMetadataOnPrimaryStorageMsg、GetVmInstanceMetadataFromPrimaryStorageMsg、ScanVmInstanceMetadataFromPrimaryStorageMsg、CleanupVmInstanceMetadataOnPrimaryStorageMsg也做同样处理。🤖 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 1794 - 1817, The default handlers in PrimaryStorageBase (handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg ...), handle(ReadVmInstanceMetadataOnPrimaryStorageMsg ...), handle(GetVmInstanceMetadataFromPrimaryStorageMsg ...), handle(ScanVmInstanceMetadataFromPrimaryStorageMsg ...), handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg ...)) must not silently succeed; change each to return a failing reply instead of a blank/true success. For each handler create the appropriate reply, call reply.setSuccess(false) and set a clear error message (e.g. "operation not supported on this primary storage") via reply.setError(...) before calling bus.reply(msg, reply) so callers can distinguish “unsupported” from “no data.”plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (1)
55-60:⚠️ Potential issue | 🟠 Major路径替换还有两个边界缺陷。
allOldPaths为null时这里会直接 NPE;另外idx > 0会漏掉 marker 恰好位于首字符的合法路径(例如/rootVolumes/...),导致oldPrefix提取失败,并把本该发生的路径替换退化成空映射。🛠️ 建议修复
String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/"; + if (allOldPaths == null || allOldPaths.isEmpty()) { + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(Collections.emptyMap()); + result.setNewPrefix(newPrefix); + return result; + } + // Extract old prefix from the first recognizable path String oldPrefix = null; for (String path : allOldPaths) { oldPrefix = extractOldPrefix(path); if (oldPrefix != null) break; @@ for (String marker : markers) { int idx = path.indexOf(marker); - if (idx > 0) { + if (idx >= 0) { return path.substring(0, idx + 1); } }Also applies to: 64-66, 81-89
🤖 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 55 - 60, Guard against allOldPaths being null before iterating and ensure extractOldPrefix treats a marker at index 0 as valid (use idx >= 0 rather than idx > 0) so we don't skip legitimate prefixes; update the loop that sets oldPrefix (variable allOldPaths and method extractOldPrefix in NfsVmMetadataExtension) to first null-check allOldPaths, iterate safely, and make extractOldPrefix return a meaningful prefix even when the marker is at the first character so oldPrefix is correctly populated (apply the same null-check/idx >= 0 fix to the other occurrences referenced around extractOldPrefix usage).plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java (1)
54-59:⚠️ Potential issue | 🟠 Major路径替换逻辑和 NFS 版本一样有边界问题。
allOldPaths为null会直接 NPE,idx > 0也会漏掉/rootVolumes/...这类以 marker 起始的合法路径,最终让本该发生的路径重写退化成空映射。🛠️ 建议修复
String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/"; + if (allOldPaths == null || allOldPaths.isEmpty()) { + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(Collections.emptyMap()); + result.setNewPrefix(newPrefix); + return result; + } + // Extract old prefix from the first recognizable path String oldPrefix = null; for (String path : allOldPaths) { oldPrefix = extractOldPrefix(path); if (oldPrefix != null) break; @@ for (String marker : markers) { int idx = path.indexOf(marker); - if (idx > 0) { + if (idx >= 0) { return path.substring(0, idx + 1); } }Also applies to: 63-65, 80-88
🤖 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 54 - 59, The loop in LocalStorageVmMetadataExtension that uses allOldPaths and extractOldPrefix can NPE when allOldPaths is null and currently skips valid paths that start with the marker because it uses an idx > 0 style check elsewhere; add a null/empty check for allOldPaths before iterating and ensure the prefix-detection logic accepts marker-at-start paths (use idx >= 0 or startsWith(marker) instead of idx > 0), and update extractOldPrefix to safely return null for bad inputs; apply the same null-check and idx >= 0 / startsWith fix to the other identical blocks referenced (the similar logic around the other extractOldPrefix usages).plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (3)
2177-2178:⚠️ Potential issue | 🟡 Minor
ScanVmMetadataCmd缺少setUuid()调用。同样的问题,应调用
cmd.setUuid(msg.getPrimaryStorageUuid())。🛠️ 建议修复
NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd(); cmd.storagePath = mountPath; +cmd.setUuid(msg.getPrimaryStorageUuid());🤖 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 2177 - 2178, The ScanVmMetadataCmd instantiation in NfsPrimaryStorageKVMBackend should set the primary storage UUID but currently only sets storagePath; update the code that creates NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd (variable cmd) to call cmd.setUuid(msg.getPrimaryStorageUuid()) in addition to cmd.storagePath = mountPath so the command includes the primary storage UUID.
2265-2266:⚠️ Potential issue | 🟡 Minor
CleanupVmMetadataCmd缺少setUuid()调用。同样的问题,应调用
cmd.setUuid(msg.getPrimaryStorageUuid())。🛠️ 建议修复
CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); cmd.metadataPath = metadataPath; +cmd.setUuid(msg.getPrimaryStorageUuid());🤖 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 2265 - 2266, The CleanupVmMetadataCmd instance is missing the primary storage UUID; update the creation in NfsPrimaryStorageKVMBackend by calling cmd.setUuid(msg.getPrimaryStorageUuid()) after initializing cmd (in addition to setting cmd.metadataPath) so the command includes the primary storage identifier (use the existing CleanupVmMetadataCmd variable name cmd and msg.getPrimaryStorageUuid()).
2137-2139:⚠️ Potential issue | 🟡 Minor
GetVmInstanceMetadataCmd缺少setUuid()调用。与其他命令对象相同的问题,应调用
cmd.setUuid(msg.getPrimaryStorageUuid())以确保 agent 端获取正确的主存储上下文。🛠️ 建议修复
GetVmInstanceMetadataCmd cmd = new GetVmInstanceMetadataCmd(); cmd.storagePath = mountPath; cmd.vmInstanceUuid = msg.getVmInstanceUuid(); +cmd.setUuid(msg.getPrimaryStorageUuid());🤖 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 2137 - 2139, The GetVmInstanceMetadataCmd instance is missing the primary storage UUID; call cmd.setUuid(msg.getPrimaryStorageUuid()) after creating the GetVmInstanceMetadataCmd (the same place where cmd.storagePath and cmd.vmInstanceUuid are set) so the agent receives the correct primary storage context; update NfsPrimaryStorageKVMBackend's GetVmInstanceMetadataCmd construction to include this setUuid call.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java (1)
927-949:⚠️ Potential issue | 🔴 Critical删除这两个子类里重复声明的
storagePath。
AgentCommand在 Line 94 已经定义了storagePath。这里再次声明会遮蔽父类字段:Line 1190 的httpCall()写的是父类字段,而 Line 3935 和 Line 3958 写的是子类字段。命令序列化时会出现同名字段冲突/歧义,这个问题之前已经提过,看起来当前提交里还没有修掉。建议修改
public static class GetVmInstanceMetadataCmd extends AgentCommand { - public String storagePath; public String vmInstanceUuid; } @@ public static class ScanVmMetadataCmd extends AgentCommand { - public String storagePath; public List<String> vmUuids; }#!/bin/bash set -euo pipefail FILE="plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java" echo "== AgentCommand base field ==" sed -n '92,95p' "$FILE" echo echo "== duplicated storagePath declarations ==" sed -n '927,950p' "$FILE" echo echo "== storagePath assignments ==" rg -n -C2 '\bstoragePath\s*=' "$FILE"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java` around lines 927 - 949, The two inner command classes GetVmInstanceMetadataCmd and ScanVmMetadataCmd duplicate the storagePath field already defined in AgentCommand, causing field shadowing and serialization ambiguity; remove the storagePath declarations from those two classes so they inherit the single storagePath from AgentCommand, then verify all assignments/readers (e.g., where httpCall() references storagePath and places that set vmUuids/vmInstanceUuid) use the inherited field and compile/serialize correctly.
🧹 Nitpick comments (3)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java (1)
208-213: 注释与实际允许操作不一致,建议修正文案。Line 208-209 提到“metadata-related reads”,但 Line 210-213 实际未放行读取类消息,仅放行
ChangeVmMetaDataMsg与销毁相关消息。建议把注释改为与白名单完全一致。🤖 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/AbstractVmInstance.java` around lines 208 - 213, The comment above VmInstanceState.Registering is inaccurate: it mentions "metadata-related reads" but the whitelist only allows ChangeVmMetaDataMsg, APIDestroyVmInstanceMsg, and DestroyVmInstanceMsg; update the comment to reflect the actual allowed operations (i.e., only ChangeVmMetaDataMsg and destroy-related messages are permitted) or, alternatively, expand the allowedOperations.addState call to include the specific metadata-read message classes if that was intended; refer to VmInstanceState.Registering, ChangeVmMetaDataMsg.class.getName(), APIDestroyVmInstanceMsg.class.getName(), and DestroyVmInstanceMsg.class.getName() to locate and fix the mismatch.compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java (1)
65-77: 建议增加psType空值检查以增强健壮性。如果主存储已被删除但卷记录仍存在,
psType查询将返回null,导致无法匹配任何扩展点,metadataPath保持为null。虽然这符合 best-effort 设计,但添加显式检查和日志可以帮助问题排查。♻️ 建议的改进
String psType = Q.New(PrimaryStorageVO.class) .select(PrimaryStorageVO_.type) .eq(PrimaryStorageVO_.uuid, psUuid) .findValue(); + if (psType == null) { + logger.debug(String.format("[MetadataExpunge] vm[uuid:%s] primary storage[uuid:%s] not found or has no type, " + + "skipping metadata cleanup", vmUuid, psUuid)); + trigger.next(); + return; + } + String metadataPath = null; for (VmMetadataPathBuildExtensionPoint ext : pluginRgty.getExtensionList(VmMetadataPathBuildExtensionPoint.class)) {🤖 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 65 - 77, The code queries psType (via Q.New(PrimaryStorageVO.class)...findValue()) and then iterates pluginRgty.getExtensionList(VmMetadataPathBuildExtensionPoint.class) to build metadataPath, but it doesn't check for psType == null; add an explicit null check after retrieving psType and log a warning (including psUuid and vmUuid) and skip the extension lookup or return early if psType is null, so metadataPath stays null intentionally; keep the existing best-effort behavior but make the null-case explicit and reference the symbols psType, psUuid, vmUuid, pluginRgty, VmMetadataPathBuildExtensionPoint and buildVmMetadataPath when adding the check and log.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2222-2252: 私有方法extractFieldsFromMetadataJson未被使用。搜索结果确认此方法未在任何地方被调用。鉴于本 PR 为大型重构,建议明确此方法的意图:若为后续功能预留,请添加 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/NfsPrimaryStorageKVMBackend.java` around lines 2222 - 2252, The private method extractFieldsFromMetadataJson in class NfsPrimaryStorageKVMBackend is unused; either remove it to eliminate dead code or explicitly document its intent if it's being reserved for future use—update the method with a clear Javadoc stating why it remains (e.g., planned for VM metadata parsing later) and expected call sites, or delete the method and remove any related imports/logging to keep the class clean; reference the method name extractFieldsFromMetadataJson and the class NfsPrimaryStorageKVMBackend when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 35-40: The code that builds the metadata path uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() which can return null; if url is null the method
returns a bogus path like "null/vm_metadata/..." and later file operations will
operate on an invalid path. Update the method in LocalStorageVmMetadataExtension
to validate the result of findValue() (the variable url) before formatting: if
url is null or empty, log an explicit error (including primaryStorageUuid) and
throw an appropriate exception (or return a clear error) rather than
constructing and returning the malformed path; ensure the error path references
the same symbols (PrimaryStorageVO_.url, primaryStorageUuid,
VmInstanceMetadataConstants.METADATA_DIR_NAME) so callers can locate the check.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 27-32: The method in NfsVmMetadataExtension that builds the
metadata path uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() to get mountPath and can return null, producing
"null/… .json"; change this to explicitly validate mountPath after the
findValue() call and if it is null or blank throw a clear
RuntimeException/CloudRuntimeException (or return an error consistent with
existing path-replacement handling in this class) instead of formatting the
path, so the code never emits a bogus "null/…" path; update any callers or tests
expecting the previous behavior accordingly and keep the error message
consistent with other checks in NfsVmMetadataExtension.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java`:
- Around line 140-142: The GlobalConfig key for VM metadata is inconsistent:
VmGlobalConfig.VM_METADATA is declared with "vm.metadata" but other modules use
"vm.metadata.enabled", causing split reads/writes; change the key in the
VmGlobalConfig.VM_METADATA GlobalConfig constructor to the canonical
"vm.metadata.enabled" and update any code references that read/write
"vm.metadata" (search for literal "vm.metadata" and replace them with
"vm.metadata.enabled") so all modules consistently use the same config key.
In `@header/src/main/java/org/zstack/header/vm/VmInstanceState.java`:
- Around line 33-56: 在 VmInstanceState 中新增的 Registering 状态缺少事务迁移定义,导致调用
nextState(...) 时抛出 CloudRuntimeException;请为枚举值 Registering 添加相应的
transactions(...) 配置(在 VmInstanceState 枚举内),定义允许的事件到目标状态的映射,类似其他状态(例如
Starting/Stopping)的事务声明,确保 Registering 包含所有应该接受的 VmInstanceStateEvent 到目标
VmInstanceState 的转换,这会修复静态初始化后状态机运行时报错。
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3457-3474: The code currently queries all connectedHostUuids but
only picks the first and calls
LocalStorageHypervisorFactory.getHypervisorBackend(...).getHypervisorBackend(self).handle(msg,
hostUuid, ...), which causes scan/cleanup to miss metadata on other hosts;
change this to fan-out the operation to every host in connectedHostUuids: for
each hostUuid obtain the LocalStorageHypervisorFactory and
LocalStorageHypervisorBackend and invoke handle(...) concurrently,
collect/aggregate all ReturnValueCompletion results (or errors) and only then
build and send the final reply for scan (merge metadata lists) and for cleanup
(either determine owner host per metadata or run cleanup on every host and merge
deletion results); ensure you preserve error aggregation and stable ordering so
behavior is deterministic and do not rely on a single host result.
- Around line 3422-3437: The current logic picks the first connected host from
connectedHostUuids and calls getHypervisorBackendFactoryByHostUuid(hostUuid),
which can route VM metadata queries to the wrong node; instead, use the VM
identifier from the incoming request (e.g., msg.getVmInstanceUuid or the
request's volume/rootVolume id) to look up which host actually stores that VM's
metadata (query the local storage metadata/volume-to-host mapping VO used by
LocalStorage, e.g., the VO that maps metadata/root volumes to hostUuid) and set
hostUuid to that owner; only if no owner is found, fall back to selecting a
connectedHostUuids entry or return the existing error. Ensure you update the
code path that currently uses connectedHostUuids.get(0) and then calls
getHypervisorBackendFactoryByHostUuid so the forwarded request hits the correct
host that holds the metadata.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java`:
- Around line 927-949: The two inner command classes GetVmInstanceMetadataCmd
and ScanVmMetadataCmd duplicate the storagePath field already defined in
AgentCommand, causing field shadowing and serialization ambiguity; remove the
storagePath declarations from those two classes so they inherit the single
storagePath from AgentCommand, then verify all assignments/readers (e.g., where
httpCall() references storagePath and places that set vmUuids/vmInstanceUuid)
use the inherited field and compile/serialize correctly.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java`:
- Around line 54-59: The loop in LocalStorageVmMetadataExtension that uses
allOldPaths and extractOldPrefix can NPE when allOldPaths is null and currently
skips valid paths that start with the marker because it uses an idx > 0 style
check elsewhere; add a null/empty check for allOldPaths before iterating and
ensure the prefix-detection logic accepts marker-at-start paths (use idx >= 0 or
startsWith(marker) instead of idx > 0), and update extractOldPrefix to safely
return null for bad inputs; apply the same null-check and idx >= 0 / startsWith
fix to the other identical blocks referenced (the similar logic around the other
extractOldPrefix usages).
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java`:
- Around line 1945-1949: The helper that calls dbf.findByUuid() (used by
getHostUuidFromVolume and located near the VolumeVO handling in
VolumeSnapshotTreeBase) must null-check the returned VolumeVO before accessing
vol.getVmInstanceUuid(); update that helper to if dbf.findByUuid(...) returns
null then return null (or propagate a clear business error) instead of
dereferencing, so getHostUuidFromVolume can detect a missing root volume and
trigger the existing reply.setError branch; ensure the helper's contract and
callers reflect the nullable return or explicit error.
- Around line 1953-1954: The code chooses a backend via getUsableBackend() even
when hostUuid is known, which can dispatch to the wrong backend across clusters;
replace calls to getUsableBackend() with getBackendByHostUuid(hostUuid) wherever
the hostUuid is available (e.g. in handlers invoking backend.handle(...) for
messages like UpdateVmInstanceMetadataOnPrimaryStorageMsg and
RebaseVolumeBackingFileOnPrimaryStorageMsg) so the backend is selected by the
actual host UUID; ensure you pass the same hostUuid used earlier (from the root
volume/request or connectedHosts) into getBackendByHostUuid(hostUuid) and keep
the existing ReturnValueCompletion handling intact.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2177-2178: The ScanVmMetadataCmd instantiation in
NfsPrimaryStorageKVMBackend should set the primary storage UUID but currently
only sets storagePath; update the code that creates
NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd (variable cmd) to call
cmd.setUuid(msg.getPrimaryStorageUuid()) in addition to cmd.storagePath =
mountPath so the command includes the primary storage UUID.
- Around line 2265-2266: The CleanupVmMetadataCmd instance is missing the
primary storage UUID; update the creation in NfsPrimaryStorageKVMBackend by
calling cmd.setUuid(msg.getPrimaryStorageUuid()) after initializing cmd (in
addition to setting cmd.metadataPath) so the command includes the primary
storage identifier (use the existing CleanupVmMetadataCmd variable name cmd and
msg.getPrimaryStorageUuid()).
- Around line 2137-2139: The GetVmInstanceMetadataCmd instance is missing the
primary storage UUID; call cmd.setUuid(msg.getPrimaryStorageUuid()) after
creating the GetVmInstanceMetadataCmd (the same place where cmd.storagePath and
cmd.vmInstanceUuid are set) so the agent receives the correct primary storage
context; update NfsPrimaryStorageKVMBackend's GetVmInstanceMetadataCmd
construction to include this setUuid call.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 55-60: Guard against allOldPaths being null before iterating and
ensure extractOldPrefix treats a marker at index 0 as valid (use idx >= 0 rather
than idx > 0) so we don't skip legitimate prefixes; update the loop that sets
oldPrefix (variable allOldPaths and method extractOldPrefix in
NfsVmMetadataExtension) to first null-check allOldPaths, iterate safely, and
make extractOldPrefix return a meaningful prefix even when the marker is at the
first character so oldPrefix is correctly populated (apply the same
null-check/idx >= 0 fix to the other occurrences referenced around
extractOldPrefix usage).
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1794-1817: The default handlers in PrimaryStorageBase
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg ...),
handle(ReadVmInstanceMetadataOnPrimaryStorageMsg ...),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg ...),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg ...),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg ...)) must not silently
succeed; change each to return a failing reply instead of a blank/true success.
For each handler create the appropriate reply, call reply.setSuccess(false) and
set a clear error message (e.g. "operation not supported on this primary
storage") via reply.setError(...) before calling bus.reply(msg, reply) so
callers can distinguish “unsupported” from “no data.”
---
Nitpick comments:
In `@compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.java`:
- Around line 208-213: The comment above VmInstanceState.Registering is
inaccurate: it mentions "metadata-related reads" but the whitelist only allows
ChangeVmMetaDataMsg, APIDestroyVmInstanceMsg, and DestroyVmInstanceMsg; update
the comment to reflect the actual allowed operations (i.e., only
ChangeVmMetaDataMsg and destroy-related messages are permitted) or,
alternatively, expand the allowedOperations.addState call to include the
specific metadata-read message classes if that was intended; refer to
VmInstanceState.Registering, ChangeVmMetaDataMsg.class.getName(),
APIDestroyVmInstanceMsg.class.getName(), and
DestroyVmInstanceMsg.class.getName() to locate and fix the mismatch.
In `@compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java`:
- Around line 65-77: The code queries psType (via
Q.New(PrimaryStorageVO.class)...findValue()) and then iterates
pluginRgty.getExtensionList(VmMetadataPathBuildExtensionPoint.class) to build
metadataPath, but it doesn't check for psType == null; add an explicit null
check after retrieving psType and log a warning (including psUuid and vmUuid)
and skip the extension lookup or return early if psType is null, so metadataPath
stays null intentionally; keep the existing best-effort behavior but make the
null-case explicit and reference the symbols psType, psUuid, vmUuid, pluginRgty,
VmMetadataPathBuildExtensionPoint and buildVmMetadataPath when adding the check
and log.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2222-2252: The private method extractFieldsFromMetadataJson in
class NfsPrimaryStorageKVMBackend is unused; either remove it to eliminate dead
code or explicitly document its intent if it's being reserved for future
use—update the method with a clear Javadoc stating why it remains (e.g., planned
for VM metadata parsing later) and expected call sites, or delete the method and
remove any related imports/logging to keep the class clean; reference the method
name extractFieldsFromMetadataJson and the class NfsPrimaryStorageKVMBackend
when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5df3daa1-27fd-4e52-a6e1-0f7056fff486
⛔ 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 (93)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.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/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/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/ReadVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.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/VmInstanceMetadataSummary.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/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/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/VmInstanceMetadataRegistrationSpec.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.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/APICreateVolumeSnapshotGroupMsg.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.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/VmInstanceMetadataSummary.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.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 with no reviewable changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
✅ Files skipped from review due to trivial changes (50)
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/storage/primary/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
🚧 Files skipped from review as they are similar to previous changes (17)
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
| String url = Q.New(PrimaryStorageVO.class) | ||
| .select(PrimaryStorageVO_.url) | ||
| .eq(PrimaryStorageVO_.uuid, primaryStorageUuid) | ||
| .findValue(); | ||
| return String.format("%s/%s/%s.json", url, | ||
| VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid); |
There was a problem hiding this comment.
url 为空时会把错误路径带进 metadata 流程。
findValue() 拿不到记录时,这里会返回 null/vm_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/LocalStorageVmMetadataExtension.java`
around lines 35 - 40, The code that builds the metadata path uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() which can return null; if url is null the method
returns a bogus path like "null/vm_metadata/..." and later file operations will
operate on an invalid path. Update the method in LocalStorageVmMetadataExtension
to validate the result of findValue() (the variable url) before formatting: if
url is null or empty, log an explicit error (including primaryStorageUuid) and
throw an appropriate exception (or return a clear error) rather than
constructing and returning the malformed path; ensure the error path references
the same symbols (PrimaryStorageVO_.url, primaryStorageUuid,
VmInstanceMetadataConstants.METADATA_DIR_NAME) so callers can locate the check.
| String mountPath = Q.New(PrimaryStorageVO.class) | ||
| .select(PrimaryStorageVO_.mountPath) | ||
| .eq(PrimaryStorageVO_.uuid, primaryStorageUuid) | ||
| .findValue(); | ||
| return String.format("%s/%s/%s.json", mountPath, | ||
| VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid); |
There was a problem hiding this comment.
mountPath 缺失时这里会生成伪路径。
findValue() 返回 null 时,这里会继续拼出 null/vm_metadata/<uuid>.json 并下发到后续 metadata 流程。既然同一个类在路径替换逻辑里已经把底层路径缺失视为需要保护的场景,这里也应该显式报错或做一致兜底,而不是继续产出一个看似合法的字符串。
🤖 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 27 - 32, The method in NfsVmMetadataExtension that builds the
metadata path uses
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue() to get mountPath and can return null, producing
"null/… .json"; change this to explicitly validate mountPath after the
findValue() call and if it is null or blank throw a clear
RuntimeException/CloudRuntimeException (or return an error consistent with
existing path-replacement handling in this class) instead of formatting the
path, so the code never emits a bogus "null/…" path; update any callers or tests
expecting the previous behavior accordingly and keep the error message
consistent with other checks in NfsVmMetadataExtension.
eecfc92 to
240ea95
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (9)
header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java (1)
16-19:⚠️ Potential issue | 🟠 Major
vmInstanceUuid外键应指向VmInstanceEO.class当前代码使用
VmInstanceVO.class,但同类实体VmMetadataFingerprintVO已使用VmInstanceEO.class。在 joined-table inheritance 模式下,外键应指向基类 EO 以确保数据库层级联删除生效。建议修改
- `@ForeignKey`(parentEntityClass = VmInstanceVO.class, onDeleteAction = ReferenceOption.CASCADE) + `@ForeignKey`(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE) private String vmInstanceUuid;同时移除未使用的 import:
import org.zstack.header.vm.VmInstanceEO; -import org.zstack.header.vm.VmInstanceVO;🤖 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 16 - 19, The ForeignKey on the vmInstanceUuid field currently references VmInstanceVO.class but should reference the entity class VmInstanceEO.class (to match VmMetadataFingerprintVO and ensure proper cascade in joined-table inheritance); update the `@ForeignKey`(parentEntityClass = ...) to use VmInstanceEO.class for the vmInstanceUuid field and remove any now-unused import(s) related to VmInstanceVO.compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java (2)
2928-2935:⚠️ Potential issue | 🟠 Major缺少 dirty service 时不要发布成功事件。
vmMetadataDirtyMarker == null也会直接bus.publish(event),API 表面成功,但没有任何 VM 被标脏。这里至少应该返回一个失败事件,让调用方感知能力缺失。🛠 建议修改
private void handle(APIUpdateVmInstanceMetadataMsg msg) { APIUpdateVmInstanceMetadataEvent event = new APIUpdateVmInstanceMetadataEvent(msg.getId()); - if (vmMetadataDirtyMarker != null) { - for (String vmUuid : msg.getVmUuids()) { - vmMetadataDirtyMarker.markDirty(vmUuid, true); - } + if (vmMetadataDirtyMarker == null) { + event.setError(operr("vm metadata dirty service is unavailable")); + bus.publish(event); + return; } + + for (String vmUuid : msg.getVmUuids()) { + vmMetadataDirtyMarker.markDirty(vmUuid, true); + } bus.publish(event); }🤖 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/VmInstanceManagerImpl.java` around lines 2928 - 2935, 在 handle(APIUpdateVmInstanceMetadataMsg msg) 中当前无论 vmMetadataDirtyMarker 是否为 null 都会直接 bus.publish(event) 导致在没有脏标记服务时错误地返回成功;在发现 vmMetadataDirtyMarker == null 时不要发布成功的 APIUpdateVmInstanceMetadataEvent,而应构造并发布一个表示失败的事件(设置合适的错误/错误码和描述,说明缺少 vmMetadataDirtyMarker 服务),否则当 vmMetadataDirtyMarker 存在时才循环调用 vmMetadataDirtyMarker.markDirty(...) 并最终发布成功事件。
2878-2922:⚠️ Potential issue | 🟠 Major不要把无法定位主存储的 VM 静默跳过。
psUuid == null时直接continue,这些 UUID 既不会发消息,也不会计入totalFailed/failedVmUuids。返回事件会少账,调用方拿不到真实清理结果。🛠 建议修改
List<String> vmUuids = msg.getVmUuids(); List<CleanupVmInstanceMetadataOnPrimaryStorageMsg> msgs = new ArrayList<>(); + List<String> unresolvedVmUuids = new ArrayList<>(); for (String vmUuid : vmUuids) { String psUuid = Q.New(VolumeVO.class) .eq(VolumeVO_.vmInstanceUuid, vmUuid) .eq(VolumeVO_.type, VolumeType.Root) .select(VolumeVO_.primaryStorageUuid) .findValue(); if (psUuid == null) { + unresolvedVmUuids.add(vmUuid); continue; } CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); cmsg.setPrimaryStorageUuid(psUuid); @@ if (msgs.isEmpty()) { evt.setTotalCleaned(0); - evt.setTotalFailed(0); - evt.setFailedVmUuids(new ArrayList<>()); + evt.setTotalFailed(unresolvedVmUuids.size()); + evt.setFailedVmUuids(unresolvedVmUuids); bus.publish(evt); return; } bus.send(msgs, new CloudBusListCallBack(msg) { `@Override` public void run(List<MessageReply> replies) { int totalCleaned = 0; - int totalFailed = 0; - List<String> failedVmUuids = new ArrayList<>(); + int totalFailed = unresolvedVmUuids.size(); + List<String> failedVmUuids = new ArrayList<>(unresolvedVmUuids); for (int i = 0; i < replies.size(); i++) { MessageReply r = replies.get(i); if (r.isSuccess()) { totalCleaned++; } else {🤖 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/VmInstanceManagerImpl.java` around lines 2878 - 2922, The loop currently silently skips VMs when psUuid == null; instead track these as failures so the event reflects true results: inside the for (String vmUuid : vmUuids) loop (referencing vmUuids, psUuid, msgs), when psUuid == null add vmUuid to a new failedVmUuidsPre list (or reuse a failedVmUuids collection) and increment a preFailedCount, then continue; update the branch that handles msgs.isEmpty() to set evt.setTotalCleaned(0), evt.setTotalFailed(preFailedCount), and evt.setFailedVmUuids(failedVmUuidsPre) so callers see these failures; finally, in the CloudBusListCallBack.run (referencing evt, msgs and the local failedVmUuids), add preFailedCount and preFailedVmUuids into the totals/failed list so the final evt merges transport failures and missing-primary-storage failures.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java (2)
57-60:⚠️ Potential issue | 🟠 Major路径替换这里还有两个边界缺陷。
allOldPaths为null时会直接 NPE;另外idx > 0会漏掉 marker 位于首字符的合法路径(例如/rootVolumes/...),导致整个替换结果为空。Also applies to: 87-89
🤖 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 57 - 60, Handle two edge cases in NfsVmMetadataExtension: guard against allOldPaths being null before iterating and accept markers at index 0. Specifically, add a null check for allOldPaths before the for-loop that computes oldPrefix (using extractOldPrefix and variable oldPrefix) and ensure the check that currently uses idx > 0 is changed to allow idx == 0 (e.g., idx >= 0) so paths with a leading marker like "/rootVolumes/..." are not skipped; apply the same null-check and idx fix for the second occurrence referenced around the other replacement (lines similar to 87-89).
27-32:⚠️ Potential issue | 🟠 Major缺少
mountPath空值保护。
findValue()取不到mountPath时,这里会继续生成null/vm_metadata/<uuid>.json,后续流程拿到的是一个伪路径。这里应在拼接前显式 fail-fast,而不是继续返回字符串。🤖 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 27 - 32, The method in NfsVmMetadataExtension builds a metadata path using mountPath from Q.New(...).findValue() but lacks null-checks; when findValue() returns null it produces an invalid path. Fix by validating the result of the query before formatting: after retrieving mountPath (from Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue()) check for null/empty and fail-fast (throw a clear exception or return an Optional/handle error) instead of proceeding to String.format(.../VmInstanceMetadataConstants.METADATA_DIR_NAME/.../vmInstanceUuid.json); ensure the error includes primaryStorageUuid and identifies the method NfsVmMetadataExtension that failed.header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java (1)
26-30:⚠️ Potential issue | 🟠 Major
metadataPath仍是必填,内容型注册入口实际不可用。现在“只传
metadataContent”会在参数校验阶段直接失败,这和这两个字段并存的接口语义不一致。至少要把metadataPath改成可选,并在 API interceptor 里显式校验metadataContent/metadataPath的互斥或“至少一个必填”约束。建议的最小修正
- `@APIParam` + `@APIParam`(required = false) private String metadataPath;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java` around lines 26 - 30, The API currently marks metadataPath as required while metadataContent is optional, which prevents callers from using the content-only flow; change the metadataPath field in APIRegisterVmInstanceFromMetadataMsg to optional (add required=false) and add an explicit validation in the corresponding API interceptor/validator that checks metadataContent and metadataPath together: require that at least one of metadataContent or metadataPath is non-empty (or enforce mutual exclusion if desired), and return a clear validation error if both are empty (or both provided when exclusive). Update the interceptor that validates APIRegisterVmInstanceFromMetadataMsg to perform this check and produce an appropriate error message.storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java (1)
1794-1817:⚠️ Potential issue | 🟠 Major基类默认成功返回会把未实现的 metadata 能力伪装成成功。
现在这些消息已经被直接路由到这里了。任何没有 override 的主存储子类都会对
Update/Read/Get/Scan/Cleanup返回成功,但实际上什么都没做,调用方会拿到误导性的结果。默认实现应当 fail-fast,明确提示子类未实现该能力。🤖 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 1794 - 1817, The current PrimaryStorageBase handlers (handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg), handle(ReadVmInstanceMetadataOnPrimaryStorageMsg), handle(GetVmInstanceMetadataFromPrimaryStorageMsg), handle(ScanVmInstanceMetadataFromPrimaryStorageMsg), handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) return empty success replies, which hides that subclasses did not implement metadata capabilities; change each default handler to fail-fast by populating the reply with an error (set success=false and attach an OperationFailure/ApiError-like error message/code) stating the metadata operation is not implemented for this primary storage and include the operation name (e.g., "UpdateVmInstanceMetadata not implemented on this PrimaryStorage") before calling bus.reply so callers receive an explicit not-implemented error instead of a silent success.plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java (2)
3418-3450:⚠️ Potential issue | 🔴 Critical不要随机选择第一台在线 host 处理单 VM metadata 查询。
LocalStorage 的 metadata 跟随根卷落到具体 host。这里直接取
connectedHostUuids.get(0),多 host 场景下会随机命中错误主机,查询结果会失败或漂移。应先根据目标 VM / 根卷定位承载 metadata 的 host,再定向下发请求。🤖 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 3418 - 3450, handle(GetVmInstanceMetadataFromPrimaryStorageMsg) currently picks connectedHostUuids.get(0) which can target the wrong host; instead locate the host that actually holds the VM's metadata (e.g. find the root volume/LocalStorageVmInstanceRefVO or VolumeVO for msg.getVmInstanceUuid() / msg.getRootVolumeUuid() and read its hostUuid) and use that hostUuid to getHypervisorBackendFactoryByHostUuid and call LocalStorageHypervisorBackend.handle; if the owner-host record is missing fall back to a clear error or existing connected-host logic but do not randomly pick the first connected host.
3453-3486:⚠️ Potential issue | 🟠 Major
scan/cleanup不能只打一台 host。LocalStorage 的 metadata 分散在各 host 本地目录上。这里虽然先查出了全部 connected hosts,最后却仍然只选第一台;
scan会天然漏扫,cleanup也可能清不到真正持有 metadata 的 host,而且这里没有order by,命中的 host 还会随数据库返回顺序漂移。scan需要 fan-out 到所有在线 host 并聚合结果,cleanup则需要先定位目标 host 或遍历所有 host。Also applies to: 3488-3524
🤖 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 3453 - 3486, The current handle(ScanVmInstanceMetadataFromPrimaryStorageMsg) selects only the first connected host, causing scan/cleanup to miss metadata; change it to fan-out the message to all connectedHostUuids returned by the SQL query, invoke getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self).handle(msg, hostUuid, ...) for each host, collect and aggregate all ScanVmInstanceMetadataFromPrimaryStorageReply results (merging found metadata and combining errors) and reply once all callbacks complete; for cleanup paths referenced around the same area, either locate the specific host that owns the target metadata before calling LocalStorageHypervisorBackend.handle or similarly iterate all connected hosts and stop early if the owner is found, ensuring deterministic ordering (e.g., sort host UUIDs) to avoid DB ordering drift.
🧹 Nitpick comments (4)
header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java (3)
52-73: 建议为 JSON 解析添加异常处理。
JSONObjectUtil.toObject(rm.vo, Map.class)在 JSON 格式错误时可能抛出异常,导致错误信息不够明确。建议捕获解析异常并包装为更具描述性的CloudRuntimeException。♻️ 建议的改进
private static void validateSingleResourceUuid(ResourceMetadata rm, String path) { if (rm.resourceUuid == null) { throw new CloudRuntimeException(String.format( "metadata %s.resourceUuid is null", path)); } if (rm.vo == null) { throw new CloudRuntimeException(String.format( "metadata %s.vo is null", path)); } - Map<String, Object> voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + Map<String, Object> voMap; + try { + voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata %s.vo is not valid JSON: %s", path, e.getMessage()), e); + } Object voUuid = voMap.get("uuid");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java` around lines 52 - 73, In validateSingleResourceUuid (class VmInstanceMetadataValidator) wrap the call to JSONObjectUtil.toObject(rm.vo, Map.class) in a try/catch that catches JSON parsing exceptions (Exception/RuntimeException), and rethrow a CloudRuntimeException that includes the metadata path and the original exception message (e.g., "failed to parse metadata {path}.vo: {cause}"); also defensively check the returned voMap for null before accessing "uuid" and throw a CloudRuntimeException with a clear message if it's null.
103-103: 文件末尾缺少换行符。建议在文件末尾添加一个空行,符合 POSIX 标准和大多数代码规范。
🤖 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/VmInstanceMetadataValidator.java` at line 103, The file VmInstanceMetadataValidator (class closing brace) is missing a trailing newline at EOF; open the file containing class VmInstanceMetadataValidator and add a single newline character at the end of the file so the file ends with an empty line to satisfy POSIX and style guidelines.
84-100: snapshotGroup 缺少 uuid 时被静默忽略,可能隐藏数据问题。当
snapshotGroups中的某个条目没有uuid字段时(line 88-90),代码直接跳过而不记录警告。如果这是有效的业务场景,建议添加注释说明;否则,建议记录警告日志以便排查潜在的数据完整性问题。另外,与上述
validateSingleResourceUuid相同,JSON 解析(lines 86, 94)也建议添加异常处理。♻️ 建议的改进
Set<String> groupUuids = new HashSet<>(); for (String groupJson : dto.snapshotGroups) { - Map<String, Object> groupMap = JSONObjectUtil.toObject(groupJson, Map.class); + Map<String, Object> groupMap; + try { + groupMap = JSONObjectUtil.toObject(groupJson, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata snapshotGroup is not valid JSON: %s", e.getMessage()), e); + } Object uuid = groupMap.get("uuid"); if (uuid != null) { groupUuids.add(uuid.toString()); + } else { + logger.warn("metadata snapshotGroup entry missing uuid field"); } } for (String refJson : dto.snapshotGroupRefs) { - Map<String, Object> refMap = JSONObjectUtil.toObject(refJson, Map.class); + Map<String, Object> refMap; + try { + refMap = JSONObjectUtil.toObject(refJson, Map.class); + } catch (Exception e) { + throw new CloudRuntimeException(String.format( + "metadata snapshotGroupRef is not valid JSON: %s", e.getMessage()), e); + } Object groupUuid = refMap.get("volumeSnapshotGroupUuid");🤖 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/VmInstanceMetadataValidator.java` around lines 84 - 100, In VmInstanceMetadataValidator update the snapshotGroups/snapshotGroupRefs handling to (1) wrap JSONObjectUtil.toObject calls for both snapshotGroups and snapshotGroupRefs in try/catch so JSON parse errors are logged (include the offending JSON and exception) and the loop continues, and (2) when a parsed group entry lacks a uuid (the uuid null branch currently ignored), emit a warning-level log referencing the snapshotGroups entry content and the validator context instead of silently skipping; apply the same parsing exception handling for ref parsing (volumeSnapshotGroupUuid) and keep the existing existence check that throws the CloudRuntimeException when a ref references a missing groupUuid.plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java (1)
2222-2252:extractFieldsFromMetadataJson方法未被新增的 handler 调用。该私有方法解析 metadata JSON 并填充
VmInstanceMetadataSummary,但在当前 diff 中的 scan handler(lines 2201-2214)直接从 agent 响应的VmMetadataScanEntry映射字段,未使用此方法。如果后续不需要,建议移除以避免死代码。🤖 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 2222 - 2252, The private method extractFieldsFromMetadataJson that parses metadata into VmInstanceMetadataSummary is dead code because the scan handler currently maps fields directly from VmMetadataScanEntry; either remove extractFieldsFromMetadataJson entirely or update the scan handler to call extractFieldsFromMetadataJson when building a VmInstanceMetadataSummary. Specifically: locate the private method extractFieldsFromMetadataJson(...) and either delete it and any related unused imports/logger usage, or modify the scan handler that creates VmInstanceMetadataSummary from VmMetadataScanEntry to invoke extractFieldsFromMetadataJson(metadataJson, summary) (ensuring metadataJson is passed and summary is the same VmInstanceMetadataSummary instance) so the parsing logic is reused. Ensure compilation by removing unused method references or adding the call and handling any JSON exceptions consistently with existing logging.
🤖 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/VmGlobalConfig.java`:
- Line 212: The static GlobalConfig field VM_METADATA_LAST_REFRESH_VERSION is
the only config missing a `@GlobalConfigValidation` annotation; either annotate
VM_METADATA_LAST_REFRESH_VERSION with `@GlobalConfigValidation` (using the
existing validation pattern in this class) or, if it intentionally requires no
validation, add an inline comment on VM_METADATA_LAST_REFRESH_VERSION explaining
why validation is omitted and that the value is a persisted version number to
aid future maintainers; reference the GlobalConfigValidation annotation and the
VM_METADATA_LAST_REFRESH_VERSION symbol when applying the change.
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java`:
- Around line 2890-2894: The constructed
CleanupVmInstanceMetadataOnPrimaryStorageMsg currently sets only
primaryStorageUuid and vmUuid, causing API-path deletions to diverge from
expunge logic which expects metadataPath; populate the message's metadataPath
before sending by calling cmsg.setMetadataPath(...) with the same
path-generation logic used in VmExpungeMetadataFlow (i.e., derive the metadata
path for the given vmUuid and psUuid using the same utility or method
VmExpungeMetadataFlow uses) so both API cleanup and VmExpungeMetadataFlow
operate on the same metadata path.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`:
- Around line 103-111: The example in
APIRegisterVmInstanceFromMetadataMsg.__example__() uses metadataPath =
"/vm-metadata/vm-uuid/metadata.json", which mismatches the project's metadata
builder format; update the metadataPath in the __example__() method to use the
same format used by the builder (e.g. "vm_metadata/<vmUuid>.json") so the API
documentation and generated Groovy templates show a valid path; ensure you
reference the metadataPath field in APIRegisterVmInstanceFromMetadataMsg and
keep the rest of the example values unchanged.
In `@header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java`:
- Around line 41-53: 为注解类型 MetadataImpact 补充缺失的 Javadoc:为成员 value()
添加说明,明确其返回类型为 Impact、可取的枚举值及其语义(例如 NONE/CONFIG/STORAGE)以及该成员在注解中代表的含义和必填场景;为成员
updateOnFailure() 添加说明,描述当为 true 时在解析或更新元数据失败时的行为(是否仍触发更新/回滚/重试等)、默认值 false
的含义以及典型使用场景。定位标识符:MetadataImpact, value(), updateOnFailure(),
Impact;保持描述简洁、面向使用者并与现有 resolver() 的 Javadoc 风格一致。
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2254-2263: In
NfsPrimaryStorageKVMBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...),
the code assumes the queried mountPath is non-null; add a null-check for the
mountPath returned by the Q.New(...).findValue() call and handle the failure
path (e.g., call completion.fail(...) with a clear message referencing the
primary storage UUID and VM UUID) instead of building metadataPath when
mountPath is null; ensure this null-check mirrors the handling used in other
handlers so the method returns/aborts cleanly if mountPath cannot be resolved.
- Around line 2170-2178: In handle(ScanVmInstanceMetadataFromPrimaryStorageMsg,
...) validate that the queried mountPath (from PrimaryStorageVO.select
PrimaryStorageVO_.mountPath) is not null before constructing and using
NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd; if mountPath is null, log
a clear error mentioning the primary storage UUID and terminate via the
completion callback (e.g., completion.fail(...) or reply with an error set on
ScanVmInstanceMetadataFromPrimaryStorageReply) to avoid NPEs—mirror the
null-check behavior used in the GetVmInstanceMetadataFromPrimaryStorageMsg
handler.
- Around line 2130-2138: The handler method
handle(GetVmInstanceMetadataFromPrimaryStorageMsg, String,
ReturnValueCompletion) uses Q.New(...).findValue() to fetch
PrimaryStorageVO_.mountPath which can be null when the primary storage record is
missing; add a null check after retrieving mountPath and if null complete with
an error (use the completion.fail or set a
GetVmInstanceMetadataFromPrimaryStorageReply with a meaningful error message)
instead of assigning a null to GetVmInstanceMetadataCmd.storagePath; ensure the
error message references the msg.getPrimaryStorageUuid() and bail out early to
avoid sending a cmd with a null storagePath to the agent.
---
Duplicate comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java`:
- Around line 2928-2935: 在 handle(APIUpdateVmInstanceMetadataMsg msg) 中当前无论
vmMetadataDirtyMarker 是否为 null 都会直接 bus.publish(event) 导致在没有脏标记服务时错误地返回成功;在发现
vmMetadataDirtyMarker == null 时不要发布成功的
APIUpdateVmInstanceMetadataEvent,而应构造并发布一个表示失败的事件(设置合适的错误/错误码和描述,说明缺少
vmMetadataDirtyMarker 服务),否则当 vmMetadataDirtyMarker 存在时才循环调用
vmMetadataDirtyMarker.markDirty(...) 并最终发布成功事件。
- Around line 2878-2922: The loop currently silently skips VMs when psUuid ==
null; instead track these as failures so the event reflects true results: inside
the for (String vmUuid : vmUuids) loop (referencing vmUuids, psUuid, msgs), when
psUuid == null add vmUuid to a new failedVmUuidsPre list (or reuse a
failedVmUuids collection) and increment a preFailedCount, then continue; update
the branch that handles msgs.isEmpty() to set evt.setTotalCleaned(0),
evt.setTotalFailed(preFailedCount), and evt.setFailedVmUuids(failedVmUuidsPre)
so callers see these failures; finally, in the CloudBusListCallBack.run
(referencing evt, msgs and the local failedVmUuids), add preFailedCount and
preFailedVmUuids into the totals/failed list so the final evt merges transport
failures and missing-primary-storage failures.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`:
- Around line 26-30: The API currently marks metadataPath as required while
metadataContent is optional, which prevents callers from using the content-only
flow; change the metadataPath field in APIRegisterVmInstanceFromMetadataMsg to
optional (add required=false) and add an explicit validation in the
corresponding API interceptor/validator that checks metadataContent and
metadataPath together: require that at least one of metadataContent or
metadataPath is non-empty (or enforce mutual exclusion if desired), and return a
clear validation error if both are empty (or both provided when exclusive).
Update the interceptor that validates APIRegisterVmInstanceFromMetadataMsg to
perform this check and produce an appropriate error message.
In `@header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java`:
- Around line 16-19: The ForeignKey on the vmInstanceUuid field currently
references VmInstanceVO.class but should reference the entity class
VmInstanceEO.class (to match VmMetadataFingerprintVO and ensure proper cascade
in joined-table inheritance); update the `@ForeignKey`(parentEntityClass = ...) to
use VmInstanceEO.class for the vmInstanceUuid field and remove any now-unused
import(s) related to VmInstanceVO.
In
`@plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java`:
- Around line 3418-3450: handle(GetVmInstanceMetadataFromPrimaryStorageMsg)
currently picks connectedHostUuids.get(0) which can target the wrong host;
instead locate the host that actually holds the VM's metadata (e.g. find the
root volume/LocalStorageVmInstanceRefVO or VolumeVO for msg.getVmInstanceUuid()
/ msg.getRootVolumeUuid() and read its hostUuid) and use that hostUuid to
getHypervisorBackendFactoryByHostUuid and call
LocalStorageHypervisorBackend.handle; if the owner-host record is missing fall
back to a clear error or existing connected-host logic but do not randomly pick
the first connected host.
- Around line 3453-3486: The current
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg) selects only the first
connected host, causing scan/cleanup to miss metadata; change it to fan-out the
message to all connectedHostUuids returned by the SQL query, invoke
getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self).handle(msg,
hostUuid, ...) for each host, collect and aggregate all
ScanVmInstanceMetadataFromPrimaryStorageReply results (merging found metadata
and combining errors) and reply once all callbacks complete; for cleanup paths
referenced around the same area, either locate the specific host that owns the
target metadata before calling LocalStorageHypervisorBackend.handle or similarly
iterate all connected hosts and stop early if the owner is found, ensuring
deterministic ordering (e.g., sort host UUIDs) to avoid DB ordering drift.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java`:
- Around line 57-60: Handle two edge cases in NfsVmMetadataExtension: guard
against allOldPaths being null before iterating and accept markers at index 0.
Specifically, add a null check for allOldPaths before the for-loop that computes
oldPrefix (using extractOldPrefix and variable oldPrefix) and ensure the check
that currently uses idx > 0 is changed to allow idx == 0 (e.g., idx >= 0) so
paths with a leading marker like "/rootVolumes/..." are not skipped; apply the
same null-check and idx fix for the second occurrence referenced around the
other replacement (lines similar to 87-89).
- Around line 27-32: The method in NfsVmMetadataExtension builds a metadata path
using mountPath from Q.New(...).findValue() but lacks null-checks; when
findValue() returns null it produces an invalid path. Fix by validating the
result of the query before formatting: after retrieving mountPath (from
Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid,
primaryStorageUuid).findValue()) check for null/empty and fail-fast (throw a
clear exception or return an Optional/handle error) instead of proceeding to
String.format(.../VmInstanceMetadataConstants.METADATA_DIR_NAME/.../vmInstanceUuid.json);
ensure the error includes primaryStorageUuid and identifies the method
NfsVmMetadataExtension that failed.
In `@storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java`:
- Around line 1794-1817: The current PrimaryStorageBase handlers
(handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg),
handle(ReadVmInstanceMetadataOnPrimaryStorageMsg),
handle(GetVmInstanceMetadataFromPrimaryStorageMsg),
handle(ScanVmInstanceMetadataFromPrimaryStorageMsg),
handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg)) return empty success
replies, which hides that subclasses did not implement metadata capabilities;
change each default handler to fail-fast by populating the reply with an error
(set success=false and attach an OperationFailure/ApiError-like error
message/code) stating the metadata operation is not implemented for this primary
storage and include the operation name (e.g., "UpdateVmInstanceMetadata not
implemented on this PrimaryStorage") before calling bus.reply so callers receive
an explicit not-implemented error instead of a silent success.
---
Nitpick comments:
In
`@header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.java`:
- Around line 52-73: In validateSingleResourceUuid (class
VmInstanceMetadataValidator) wrap the call to JSONObjectUtil.toObject(rm.vo,
Map.class) in a try/catch that catches JSON parsing exceptions
(Exception/RuntimeException), and rethrow a CloudRuntimeException that includes
the metadata path and the original exception message (e.g., "failed to parse
metadata {path}.vo: {cause}"); also defensively check the returned voMap for
null before accessing "uuid" and throw a CloudRuntimeException with a clear
message if it's null.
- Line 103: The file VmInstanceMetadataValidator (class closing brace) is
missing a trailing newline at EOF; open the file containing class
VmInstanceMetadataValidator and add a single newline character at the end of the
file so the file ends with an empty line to satisfy POSIX and style guidelines.
- Around line 84-100: In VmInstanceMetadataValidator update the
snapshotGroups/snapshotGroupRefs handling to (1) wrap JSONObjectUtil.toObject
calls for both snapshotGroups and snapshotGroupRefs in try/catch so JSON parse
errors are logged (include the offending JSON and exception) and the loop
continues, and (2) when a parsed group entry lacks a uuid (the uuid null branch
currently ignored), emit a warning-level log referencing the snapshotGroups
entry content and the validator context instead of silently skipping; apply the
same parsing exception handling for ref parsing (volumeSnapshotGroupUuid) and
keep the existing existence check that throws the CloudRuntimeException when a
ref references a missing groupUuid.
In
`@plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java`:
- Around line 2222-2252: The private method extractFieldsFromMetadataJson that
parses metadata into VmInstanceMetadataSummary is dead code because the scan
handler currently maps fields directly from VmMetadataScanEntry; either remove
extractFieldsFromMetadataJson entirely or update the scan handler to call
extractFieldsFromMetadataJson when building a VmInstanceMetadataSummary.
Specifically: locate the private method extractFieldsFromMetadataJson(...) and
either delete it and any related unused imports/logger usage, or modify the scan
handler that creates VmInstanceMetadataSummary from VmMetadataScanEntry to
invoke extractFieldsFromMetadataJson(metadataJson, summary) (ensuring
metadataJson is passed and summary is the same VmInstanceMetadataSummary
instance) so the parsing logic is reused. Ensure compilation by removing unused
method references or adding the call and handling any JSON exceptions
consistently with existing logging.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e2b3b754-ce5e-44b4-bdca-820b819d76b1
⛔ 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 (93)
compute/src/main/java/org/zstack/compute/vm/AbstractVmInstance.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/VmInstanceBase.javacompute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.javaconf/db/upgrade/V5.0.0__schema.sqlheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovyheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.javaheader/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovyheader/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/ReadVmInstanceMetadataOnPrimaryStorageMsg.javaheader/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.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/VmInstanceMetadataSummary.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/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/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/VmInstanceMetadataRegistrationSpec.javaheader/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataValidator.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.javaheader/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.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/APICreateVolumeSnapshotGroupMsg.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.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/VmInstanceMetadataSummary.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.javasimulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.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 with no reviewable changes (1)
- compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
✅ Files skipped from review due to trivial changes (48)
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/VmInstanceState.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageReply.java
- sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCanonicalEvents.java
- header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java
- sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java
- header/src/main/java/org/zstack/header/storage/primary/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java
- conf/db/upgrade/V5.0.0__schema.sql
- header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java
- sdk/src/main/java/org/zstack/sdk/VmInstanceMetadataSummary.java
- header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java
- plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
- testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
🚧 Files skipped from review as they are similar to previous changes (22)
- header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java
- header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java
- header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java
- header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java
- testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java
- header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataRegistrationSpec.java
- header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy
- header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java
- testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy
- header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java
- simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulator.java
- simulator/simulatorImpl/src/main/java/org/zstack/simulator/storage/primary/nfs/NfsPrimaryStorageSimulatorConfig.java
- plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
- header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy
- sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java
- header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java
| @GlobalConfigValidation(numberGreaterThan = 0) | ||
| public static GlobalConfig VM_METADATA_DELETE_BASE_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.delete.baseDelaySec"); | ||
|
|
||
| public static GlobalConfig VM_METADATA_LAST_REFRESH_VERSION = new GlobalConfig(CATEGORY, "vm.metadata.lastRefreshVersion"); |
There was a problem hiding this comment.
VM_METADATA_LAST_REFRESH_VERSION 缺少 @GlobalConfigValidation 注解。
该字段是本文件中唯一一个没有 @GlobalConfigValidation 注解的配置项。如果这是一个需要持久化的版本号字段且不需要校验,建议添加注释说明原因以便后续维护理解。
🤖 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` at line 212,
The static GlobalConfig field VM_METADATA_LAST_REFRESH_VERSION is the only
config missing a `@GlobalConfigValidation` annotation; either annotate
VM_METADATA_LAST_REFRESH_VERSION with `@GlobalConfigValidation` (using the
existing validation pattern in this class) or, if it intentionally requires no
validation, add an inline comment on VM_METADATA_LAST_REFRESH_VERSION explaining
why validation is omitted and that the value is a persisted version number to
aid future maintainers; reference the GlobalConfigValidation annotation and the
VM_METADATA_LAST_REFRESH_VERSION symbol when applying the change.
| CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); | ||
| cmsg.setPrimaryStorageUuid(psUuid); | ||
| cmsg.setVmUuid(vmUuid); | ||
| bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); | ||
| msgs.add(cmsg); |
There was a problem hiding this comment.
补上 metadataPath,否则 API 清理和 expunge 清理会走出两套契约。
这里构造的 CleanupVmInstanceMetadataOnPrimaryStorageMsg 只带了 primaryStorageUuid/vmUuid,但 VmExpungeMetadataFlow 对同一消息还会先解析并填充 metadataPath。后端一旦按 metadataPath 删除元数据,这条 API 路径就会变成 no-op 或失败。
🤖 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/VmInstanceManagerImpl.java`
around lines 2890 - 2894, The constructed
CleanupVmInstanceMetadataOnPrimaryStorageMsg currently sets only
primaryStorageUuid and vmUuid, causing API-path deletions to diverge from
expunge logic which expects metadataPath; populate the message's metadataPath
before sending by calling cmsg.setMetadataPath(...) with the same
path-generation logic used in VmExpungeMetadataFlow (i.e., derive the metadata
path for the given vmUuid and psUuid using the same utility or method
VmExpungeMetadataFlow uses) so both API cleanup and VmExpungeMetadataFlow
operate on the same metadata path.
| public static APIRegisterVmInstanceFromMetadataMsg __example__() { | ||
| APIRegisterVmInstanceFromMetadataMsg msg = new APIRegisterVmInstanceFromMetadataMsg(); | ||
| msg.metadataPath = "/vm-metadata/vm-uuid/metadata.json"; | ||
| msg.targetPrimaryStorageUuid = uuid(); | ||
| msg.zoneUuid = uuid(); | ||
| msg.clusterUuid = uuid(); | ||
| msg.hostUuid = uuid(); | ||
| msg.name = "my-restored-vm"; | ||
| return msg; |
There was a problem hiding this comment.
__example__() 里的路径格式和当前 metadata builder 不一致。
同一 PR 里 metadata 路径是按 vm_metadata/<vmUuid>.json 生成的;这里的 /vm-metadata/vm-uuid/metadata.json 会把错误格式写进 API 文档示例,调用方很容易照抄出无效路径。As per coding guidelines, "API 类需要实现 example 方法以便生成 API 文档,并确保生成对应的 Groovy API Template 与 API Markdown 文件。"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java`
around lines 103 - 111, The example in
APIRegisterVmInstanceFromMetadataMsg.__example__() uses metadataPath =
"/vm-metadata/vm-uuid/metadata.json", which mismatches the project's metadata
builder format; update the metadataPath in the __example__() method to use the
same format used by the builder (e.g. "vm_metadata/<vmUuid>.json") so the API
documentation and generated Groovy templates show a valid path; ensure you
reference the metadataPath field in APIRegisterVmInstanceFromMetadataMsg and
keep the rest of the example values unchanged.
| Impact value(); | ||
|
|
||
| /** | ||
| * Spring bean name of the {@link VmUuidFromApiResolver} that extracts vmUuid(s) | ||
| * from this API message. Must be specified for {@link Impact#CONFIG} and | ||
| * {@link Impact#STORAGE}; ignored for {@link Impact#NONE}. | ||
| * | ||
| * <p>The bean must be registered in Spring XML. Interceptor looks it up at | ||
| * startup via {@code ComponentLoader.getComponentByBeanName()}.</p> | ||
| */ | ||
| String resolver() default ""; | ||
|
|
||
| boolean updateOnFailure() default false; |
There was a problem hiding this comment.
请为注解成员 value() 与 updateOnFailure() 补充 Javadoc。
Line 41 和 Line 53 目前缺少方法级注释;在注解类型中这些成员同样属于接口方法,建议补齐文档以满足规范并减少误用。
✍️ 建议修改
public `@interface` MetadataImpact {
+ /**
+ * 元数据影响级别。
+ */
Impact value();
@@
+ /**
+ * 是否在 API 失败后仍触发元数据更新。
+ */
boolean updateOnFailure() default false;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.
| Impact value(); | |
| /** | |
| * Spring bean name of the {@link VmUuidFromApiResolver} that extracts vmUuid(s) | |
| * from this API message. Must be specified for {@link Impact#CONFIG} and | |
| * {@link Impact#STORAGE}; ignored for {@link Impact#NONE}. | |
| * | |
| * <p>The bean must be registered in Spring XML. Interceptor looks it up at | |
| * startup via {@code ComponentLoader.getComponentByBeanName()}.</p> | |
| */ | |
| String resolver() default ""; | |
| boolean updateOnFailure() default false; | |
| public `@interface` MetadataImpact { | |
| /** | |
| * 元数据影响级别。 | |
| */ | |
| Impact value(); | |
| /** | |
| * Spring bean name of the {`@link` VmUuidFromApiResolver} that extracts vmUuid(s) | |
| * from this API message. Must be specified for {`@link` Impact#CONFIG} and | |
| * {`@link` Impact#STORAGE}; ignored for {`@link` Impact#NONE}. | |
| * | |
| * <p>The bean must be registered in Spring XML. Interceptor looks it up at | |
| * startup via {`@code` ComponentLoader.getComponentByBeanName()}.</p> | |
| */ | |
| String resolver() default ""; | |
| /** | |
| * 是否在 API 失败后仍触发元数据更新。 | |
| */ | |
| boolean updateOnFailure() default false; | |
| } |
🤖 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/MetadataImpact.java`
around lines 41 - 53, 为注解类型 MetadataImpact 补充缺失的 Javadoc:为成员 value()
添加说明,明确其返回类型为 Impact、可取的枚举值及其语义(例如 NONE/CONFIG/STORAGE)以及该成员在注解中代表的含义和必填场景;为成员
updateOnFailure() 添加说明,描述当为 true 时在解析或更新元数据失败时的行为(是否仍触发更新/回滚/重试等)、默认值 false
的含义以及典型使用场景。定位标识符:MetadataImpact, value(), updateOnFailure(),
Impact;保持描述简洁、面向使用者并与现有 resolver() 的 Javadoc 风格一致。
| @Override | ||
| public void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<GetVmInstanceMetadataFromPrimaryStorageReply> completion) { | ||
| String mountPath = Q.New(PrimaryStorageVO.class) | ||
| .select(PrimaryStorageVO_.mountPath) | ||
| .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) | ||
| .findValue(); | ||
|
|
||
| GetVmInstanceMetadataCmd cmd = new GetVmInstanceMetadataCmd(); | ||
| cmd.storagePath = mountPath; |
There was a problem hiding this comment.
mountPath 可能为 null,应增加校验。
当 primaryStorageUuid 对应的记录不存在时,Q.New(...).findValue() 返回 null,后续 cmd.storagePath = mountPath 会将 null 传递给 agent。建议在使用前校验 mountPath 是否为 null,并返回明确的错误。
🛡️ 建议添加 null 校验
`@Override`
public void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<GetVmInstanceMetadataFromPrimaryStorageReply> completion) {
String mountPath = Q.New(PrimaryStorageVO.class)
.select(PrimaryStorageVO_.mountPath)
.eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid())
.findValue();
+ if (mountPath == null) {
+ completion.fail(operr("cannot find mountPath for primary storage[uuid:%s]", msg.getPrimaryStorageUuid()));
+ return;
+ }
GetVmInstanceMetadataCmd cmd = new GetVmInstanceMetadataCmd();🤖 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 2130 - 2138, The handler method
handle(GetVmInstanceMetadataFromPrimaryStorageMsg, String,
ReturnValueCompletion) uses Q.New(...).findValue() to fetch
PrimaryStorageVO_.mountPath which can be null when the primary storage record is
missing; add a null check after retrieving mountPath and if null complete with
an error (use the completion.fail or set a
GetVmInstanceMetadataFromPrimaryStorageReply with a meaningful error message)
instead of assigning a null to GetVmInstanceMetadataCmd.storagePath; ensure the
error message references the msg.getPrimaryStorageUuid() and bail out early to
avoid sending a cmd with a null storagePath to the agent.
| @Override | ||
| public void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<ScanVmInstanceMetadataFromPrimaryStorageReply> completion) { | ||
| String mountPath = Q.New(PrimaryStorageVO.class) | ||
| .select(PrimaryStorageVO_.mountPath) | ||
| .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) | ||
| .findValue(); | ||
|
|
||
| NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd(); | ||
| cmd.storagePath = mountPath; |
There was a problem hiding this comment.
mountPath 可能为 null,与 GetVmInstanceMetadataFromPrimaryStorageMsg handler 相同的问题。
建议在使用前校验 mountPath 是否为 null。
🤖 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 2170 - 2178, In handle(ScanVmInstanceMetadataFromPrimaryStorageMsg,
...) validate that the queried mountPath (from PrimaryStorageVO.select
PrimaryStorageVO_.mountPath) is not null before constructing and using
NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd; if mountPath is null, log
a clear error mentioning the primary storage UUID and terminate via the
completion callback (e.g., completion.fail(...) or reply with an error set on
ScanVmInstanceMetadataFromPrimaryStorageReply) to avoid NPEs—mirror the
null-check behavior used in the GetVmInstanceMetadataFromPrimaryStorageMsg
handler.
| @Override | ||
| public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion<CleanupVmInstanceMetadataOnPrimaryStorageReply> completion) { | ||
| String metadataPath = msg.getMetadataPath(); | ||
| if (metadataPath == null) { | ||
| String mountPath = Q.New(PrimaryStorageVO.class) | ||
| .select(PrimaryStorageVO_.mountPath) | ||
| .eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()) | ||
| .findValue(); | ||
| metadataPath = String.format("%s/%s/%s.json", mountPath, VmInstanceMetadataConstants.METADATA_DIR_NAME, msg.getVmUuid()); | ||
| } |
There was a problem hiding this comment.
mountPath 可能为 null,与其他 handler 相同的问题。
当 msg.getMetadataPath() 为 null 时,代码会查询 mountPath,但未校验查询结果。
🤖 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 2254 - 2263, In
NfsPrimaryStorageKVMBackend.handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg...),
the code assumes the queried mountPath is non-null; add a null-check for the
mountPath returned by the Q.New(...).findValue() call and handle the failure
path (e.g., call completion.fail(...) with a clear message referencing the
primary storage UUID and VM UUID) instead of building metadataPath when
mountPath is null; ensure this null-check mirrors the handling used in other
handlers so the method returns/aborts cleanly if mountPath cannot be resolved.
2723710 to
be705aa
Compare
Resolves: ZSV-10000
be705aa to
f70e72b
Compare
Resolves: ZSV-10000
sync from gitlab !9411