diff --git a/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java b/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java index bd79900c13c..b24c2a7b4e1 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java @@ -133,4 +133,40 @@ public class VmGlobalConfig { @GlobalConfigValidation(validValues = {"None", "AuthenticAMD"}) @BindResourceConfig(value = {VmInstanceVO.class}) public static GlobalConfig VM_CPUID_VENDOR = new GlobalConfig(CATEGORY, "vm.cpuid.vendor"); + + @GlobalConfigValidation(numberGreaterThan = 1) + public static GlobalConfig GC_INTERVAL = new GlobalConfig(CATEGORY, "deletion.gcInterval"); + + @GlobalConfigValidation(validValues = {"true", "false"}) + public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata"); + + @GlobalConfigDef(defaultValue = "5", type = Integer.class, + description = "Max concurrent metadata writes per primary storage per MN") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_PS_MAX_CONCURRENT = new GlobalConfig(CATEGORY, "vm.metadata.ps.maxConcurrent"); + + @GlobalConfigDef(defaultValue = "10", type = Integer.class, + description = "Max concurrent VM metadata updates globally per MN") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_GLOBAL_MAX_CONCURRENT = new GlobalConfig(CATEGORY, "vm.metadata.global.maxConcurrent"); + + @GlobalConfigDef(defaultValue = "10", type = Integer.class, + description = "Initial GC delay in seconds after API success") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_GC_INITIAL_DELAY_SEC = new GlobalConfig(CATEGORY, "vm.metadata.gc.initialDelaySec"); + + @GlobalConfigDef(defaultValue = "5", type = Integer.class, + description = "Max retry count before giving up metadata flush") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_MAX_RETRY = new GlobalConfig(CATEGORY, "vm.metadata.maxRetry"); + + @GlobalConfigDef(defaultValue = "5", type = Long.class, + description = "Dirty poller interval in seconds") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DIRTY_POLL_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.dirty.pollIntervalSec"); + + @GlobalConfigDef(defaultValue = "20", type = Integer.class, + description = "Max dirty rows to claim per poller cycle") + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DIRTY_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.dirty.batchSize"); } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java index c17cf5d5179..0ce04419dcb 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java @@ -21,6 +21,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.network.l2.*; import org.zstack.header.network.l3.*; +import org.zstack.header.storage.primary.APIRegisterVmInstanceMsg; import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO; import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO_; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index e31bc001218..d8be22b568c 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -45,6 +45,15 @@ import org.zstack.header.message.*; import org.zstack.header.network.l3.*; import org.zstack.header.storage.primary.*; +import org.zstack.header.storage.snapshot.*; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO_; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.tag.SystemTagVO_; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.zstack.header.vm.*; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicHostUuid; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicVmState; @@ -66,23 +75,19 @@ import org.zstack.network.l3.L3NetworkManager; import org.zstack.network.service.DnsUtils; import org.zstack.network.service.NetworkServiceManager; -import org.zstack.resourceconfig.ResourceConfig; -import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.resourceconfig.*; import org.zstack.tag.SystemTagCreator; import org.zstack.tag.SystemTagUtils; import org.zstack.tag.TagManager; -import org.zstack.utils.CollectionUtils; -import org.zstack.utils.ExceptionDSL; -import org.zstack.utils.ObjectUtils; -import org.zstack.utils.Utils; +import org.zstack.utils.*; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.function.Function; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; -import org.zstack.utils.network.NicIpAddressInfo; import org.zstack.utils.network.IPv6Constants; import org.zstack.utils.network.IPv6NetworkUtils; import org.zstack.utils.network.NetworkUtils; +import org.zstack.utils.network.NicIpAddressInfo; import javax.persistence.PersistenceException; import javax.persistence.Tuple; @@ -90,6 +95,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static java.util.Arrays.asList; @@ -140,6 +146,10 @@ public class VmInstanceBase extends AbstractVmInstance { private VmInstanceResourceMetadataManager vidm; @Autowired private NetworkServiceManager nwServiceMgr; + @Autowired + private ResourceDestinationMaker destMaker; + @Autowired + private org.zstack.compute.vm.metadata.VmMetadataBuilder vmMetadataBuilder; protected VmInstanceVO self; protected VmInstanceVO originalCopy; @@ -533,6 +543,8 @@ protected void handleLocalMessage(Message msg) { handle((CancelFlattenVmInstanceMsg) msg); } else if (msg instanceof KvmReportVmShutdownEventMsg) { handle((KvmReportVmShutdownEventMsg) msg); + } else if (msg instanceof UpdateVmInstanceMetadataMsg) { + handle((UpdateVmInstanceMetadataMsg) msg); } else { VmInstanceBaseExtensionFactory ext = vmMgr.getVmInstanceBaseExtensionFactory(msg); if (ext != null) { @@ -9369,5 +9381,80 @@ public void run(MessageReply reply) { } }); } -} + /** + * 处理元数据更新消息。 + * + *

通过 ChainTask 确保同一 VM 的元数据更新串行执行。 + * 该消息由 VmMetadataDirtyMarker 发送到本地 VM 服务, + * 内部从 DB 全量构建 metadata payload 后写入主存储。

+ * + *

失败路径直接返回错误 reply,由 VmMetadataDirtyMarker 的 + * onFlushFailure() 统一处理重试和指数退避。

+ */ + private void handle(UpdateVmInstanceMetadataMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("handle-update-vm-%s-metadata", msg.getUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + doHandleUpdateVmInstanceMetadata(msg); + chain.next(); + } + + @Override + public String getName() { + return String.format("handle-update-vm-%s-metadata-task", msg.getUuid()); + } + }); + } + + private void doHandleUpdateVmInstanceMetadata(UpdateVmInstanceMetadataMsg msg) { + // 1. 构建 payload(通过 VmMetadataBuilder 在 @Transactional(readOnly=true) 事务内完成) + String metadata = vmMetadataBuilder.buildVmInstanceMetadata(msg.getUuid()); + + // 2. Payload 大小保护 + int payloadSize = metadata.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (payloadSize > org.zstack.compute.vm.metadata.VmMetadataBuilder.REJECT_THRESHOLD) { + logger.error(String.format("metadata payload too large: %d bytes for vm[uuid:%s], rejecting", + payloadSize, msg.getUuid())); + MessageReply reply = new MessageReply(); + reply.setError(Platform.operr("metadata payload too large (%d bytes, limit %d) for vm[uuid=%s]", + payloadSize, org.zstack.compute.vm.metadata.VmMetadataBuilder.REJECT_THRESHOLD, msg.getUuid())); + bus.reply(msg, reply); + return; + } + if (payloadSize > org.zstack.compute.vm.metadata.VmMetadataBuilder.WARN_THRESHOLD) { + logger.warn(String.format("metadata payload large: %d bytes for vm[uuid:%s]", + payloadSize, msg.getUuid())); + } + + // 3. 发送到主存储 + Tuple tuple = Q.New(VolumeVO.class).select(VolumeVO_.primaryStorageUuid, VolumeVO_.uuid) + .eq(VolumeVO_.vmInstanceUuid, msg.getUuid()).eq(VolumeVO_.type, VolumeType.Root).findTuple(); + String primaryStorageUuid = tuple.get(0, String.class); + String rootVolumeUuid = tuple.get(1, String.class); + + UpdateVmInstanceMetadataOnPrimaryStorageMsg umsg = new UpdateVmInstanceMetadataOnPrimaryStorageMsg(); + umsg.setMetadata(metadata); + umsg.setPrimaryStorageUuid(primaryStorageUuid); + umsg.setRootVolumeUuid(rootVolumeUuid); + umsg.setStorageStructureChange(msg.isStorageStructureChange()); + bus.makeLocalServiceId(umsg, PrimaryStorageConstant.SERVICE_ID); + bus.send(umsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + + if (!r.isSuccess()) { + reply.setError(Platform.operr("failed to update vm[uuid=%s] metadata on primary storage", + msg.getUuid()).withCause(r.getError())); + } + bus.reply(msg, reply); + } + }); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceMetadataFieldProcessor.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceMetadataFieldProcessor.java new file mode 100644 index 00000000000..e2b74d42f02 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceMetadataFieldProcessor.java @@ -0,0 +1,233 @@ +package org.zstack.compute.vm; + +import org.zstack.header.vm.VmInstanceMetadataDTO; +import org.zstack.header.vm.VmInstanceMetadataRegistrationSpec; +import org.zstack.utils.gson.JSONObjectUtil; + +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * 虚拟机元数据注册时的字段处理器。 + * + *

根据"注册字段处理矩阵"的规则,对反序列化后的 VO JSON 字段执行: + * 保留 / 替换 / 设 null / 重新生成 / 硬编码 等操作。

+ * + *

处理采用 Map 操作方式(而非反序列化为具体 VO 类), + * 避免字段类型变更导致的兼容性问题。

+ * + * @see VmInstanceMetadataRegistrationSpec + */ +public class VmInstanceMetadataFieldProcessor { + + private VmInstanceMetadataFieldProcessor() { + } + + // ================================================================ + // VmInstanceVO + // ================================================================ + + /** + * VmInstanceVO 中注册时需要设为 null 的字段。 + */ + private static final Set VM_NULL_FIELDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "clusterUuid", + "hostUuid", + "lastHostUuid", + "instanceOfferingUuid", + "defaultL3NetworkUuid", + "managementNetworkUuid" + ))); + + /** + * 处理 VmInstanceVO JSON。 + * + *

处理规则: + *

+ * + * @param vmVoJson 原始 VmInstanceVO JSON + * @param spec 注册参数 + * @return 处理后的 VmInstanceVO JSON + */ + @SuppressWarnings("unchecked") + public static String processVmInstanceVO(String vmVoJson, VmInstanceMetadataRegistrationSpec spec) { + Map voMap = JSONObjectUtil.toObject(vmVoJson, LinkedHashMap.class); + + for (String field : VM_NULL_FIELDS) { + voMap.put(field, null); + } + + voMap.put("zoneUuid", spec.getZoneUuid()); + voMap.put("accountUuid", spec.getAccountUuid()); + voMap.put("state", "Stopped"); + + return JSONObjectUtil.toJsonString(voMap); + } + + // ================================================================ + // VolumeVO + // ================================================================ + + /** + * VolumeVO 中注册时需要设为 null 的字段。 + */ + private static final Set VOLUME_NULL_FIELDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "diskOfferingUuid" + ))); + + /** + * 处理 VolumeVO JSON。 + * + *

处理规则: + *

    + *
  • uuid/vmInstanceUuid/name/size/type/format → 保留
  • + *
  • primaryStorageUuid → 替换为 spec 中的新主存储 UUID
  • + *
  • installPath → 路径标识符替换
  • + *
  • diskOfferingUuid → 设 null
  • + *
  • accountUuid → 替换为 spec 中的调用者
  • + *
+ * + * @param volumeVoJson 原始 VolumeVO JSON + * @param spec 注册参数 + * @return 处理后的 VolumeVO JSON + */ + @SuppressWarnings("unchecked") + public static String processVolumeVO(String volumeVoJson, VmInstanceMetadataRegistrationSpec spec) { + Map voMap = JSONObjectUtil.toObject(volumeVoJson, LinkedHashMap.class); + + for (String field : VOLUME_NULL_FIELDS) { + voMap.put(field, null); + } + + voMap.put("primaryStorageUuid", spec.getPrimaryStorageUuid()); + voMap.put("accountUuid", spec.getAccountUuid()); + + replaceInstallPath(voMap, "installPath", spec); + + return JSONObjectUtil.toJsonString(voMap); + } + + // ================================================================ + // VolumeSnapshotVO + // ================================================================ + + /** + * 处理 VolumeSnapshotVO JSON。 + * + *

处理规则: + *

    + *
  • uuid/volumeUuid/parentUuid/treeUuid/latest → 保留
  • + *
  • primaryStorageUuid → 替换为 spec 中的新主存储 UUID
  • + *
  • primaryStorageInstallPath → 路径标识符替换
  • + *
+ * + * @param snapshotVoJson 原始 VolumeSnapshotVO JSON + * @param spec 注册参数 + * @return 处理后的 VolumeSnapshotVO JSON + */ + @SuppressWarnings("unchecked") + public static String processVolumeSnapshotVO(String snapshotVoJson, VmInstanceMetadataRegistrationSpec spec) { + Map voMap = JSONObjectUtil.toObject(snapshotVoJson, LinkedHashMap.class); + + voMap.put("primaryStorageUuid", spec.getPrimaryStorageUuid()); + + replaceInstallPath(voMap, "primaryStorageInstallPath", spec); + + return JSONObjectUtil.toJsonString(voMap); + } + + // ================================================================ + // SystemTagVO / ResourceConfigVO + // ================================================================ + + /** + * 处理 SystemTagVO JSON:为 uuid 生成新值,移除自增 id。 + * + * @param tagJson 原始 SystemTagVO JSON + * @param uuidSupplier UUID 生成器(通常为 Platform::getUuid) + * @return 处理后的 SystemTagVO JSON + */ + @SuppressWarnings("unchecked") + public static String processSystemTagVO(String tagJson, Supplier uuidSupplier) { + Map tagMap = JSONObjectUtil.toObject(tagJson, LinkedHashMap.class); + + tagMap.put("uuid", uuidSupplier.get()); + tagMap.remove("id"); + + return JSONObjectUtil.toJsonString(tagMap); + } + + /** + * 处理 ResourceConfigVO JSON:为 uuid 生成新值,移除自增 id。 + * + * @param configJson 原始 ResourceConfigVO JSON + * @param uuidSupplier UUID 生成器(通常为 Platform::getUuid) + * @return 处理后的 ResourceConfigVO JSON + */ + @SuppressWarnings("unchecked") + public static String processResourceConfigVO(String configJson, Supplier uuidSupplier) { + Map configMap = JSONObjectUtil.toObject(configJson, LinkedHashMap.class); + + configMap.put("uuid", uuidSupplier.get()); + configMap.remove("id"); + + return JSONObjectUtil.toJsonString(configMap); + } + + // ================================================================ + // 跨存储过滤 + // ================================================================ + + /** + * 判断 volume 的 installPath 是否属于指定主存储。 + * + * @param volumeVoJson VolumeVO JSON + * @param pathIdentifier 存储路径标识符(如 vg uuid 或挂载路径前缀) + * @return true 表示属于该主存储 + */ + @SuppressWarnings("unchecked") + public static boolean belongsToPrimaryStorage(String volumeVoJson, String pathIdentifier) { + Map voMap = JSONObjectUtil.toObject(volumeVoJson, LinkedHashMap.class); + String installPath = (String) voMap.get("installPath"); + return installPath != null && installPath.contains(pathIdentifier); + } + + /** + * 过滤出属于指定主存储的 volume UUID 集合。 + * + *

注册时,仅处理属于当前存储的 volume 及其关联快照。 + * 不属于当前存储的 volume 跳过。

+ * + * @param dto 完整元数据 DTO + * @param pathIdentifier 旧存储路径标识符 + * @return 属于该存储的 volume resourceUuid 集合 + */ + public static Set filterVolumesByStorage(VmInstanceMetadataDTO dto, String pathIdentifier) { + if (dto.volumes == null) { + return Collections.emptySet(); + } + return dto.volumes.stream() + .filter(rm -> belongsToPrimaryStorage(rm.vo, pathIdentifier)) + .map(rm -> rm.resourceUuid) + .collect(Collectors.toSet()); + } + + // ================================================================ + // 内部工具 + // ================================================================ + + private static void replaceInstallPath(Map voMap, String fieldName, + VmInstanceMetadataRegistrationSpec spec) { + String path = (String) voMap.get(fieldName); + if (path != null && spec.getOldPathIdentifier() != null && spec.getNewPathIdentifier() != null) { + voMap.put(fieldName, path.replace(spec.getOldPathIdentifier(), spec.getNewPathIdentifier())); + } + } +} \ No newline at end of file diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java index 33afa043278..1f916560f58 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java @@ -2,22 +2,29 @@ import org.apache.commons.collections.CollectionUtils; import org.zstack.core.Platform; +import org.zstack.core.db.Q; import org.zstack.header.configuration.InstanceOfferingInventory; import org.zstack.header.errorcode.OperationFailureException; -import org.zstack.header.vm.APIChangeInstanceOfferingMsg; -import org.zstack.header.vm.APICreateVmInstanceMsg; -import org.zstack.header.vm.CreateVmInstanceMsg; -import org.zstack.header.vm.DiskAO; -import org.zstack.header.vm.UpdateVmInstanceMsg; -import org.zstack.header.vm.UpdateVmInstanceSpec; -import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO_; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.tag.SystemTagVO_; +import org.zstack.header.vm.*; +import org.zstack.header.volume.VolumeVO; +import org.zstack.header.volume.VolumeVO_; +import org.zstack.resourceconfig.ResourceConfigVO; +import org.zstack.resourceconfig.ResourceConfigVO_; import org.zstack.tag.SystemTagUtils; +import org.zstack.utils.function.ForEachFunction; +import org.zstack.utils.gson.JSONObjectUtil; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; import static org.zstack.compute.vm.VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME; diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/DefaultVmUuidFromApiResolver.java b/compute/src/main/java/org/zstack/compute/vm/metadata/DefaultVmUuidFromApiResolver.java new file mode 100644 index 00000000000..75e3122184a --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/DefaultVmUuidFromApiResolver.java @@ -0,0 +1,27 @@ +package org.zstack.compute.vm.metadata; + +import org.zstack.header.message.APIMessage; +import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.VmUuidFromApiResolver; + +import java.util.Collections; +import java.util.List; + +/** + * 默认 VM UUID 解析器:从实现 {@link VmInstanceMessage} 接口的 API 消息中直接获取 vmInstanceUuid。 + * + *

覆盖绝大多数 VM 直接 API(如 APIUpdateVmInstanceMsg、APIStartVmInstanceMsg 等)。

+ */ +public class DefaultVmUuidFromApiResolver implements VmUuidFromApiResolver { + + @Override + public boolean supports(APIMessage msg) { + return msg instanceof VmInstanceMessage; + } + + @Override + public List resolveVmUuids(APIMessage msg) { + String vmUuid = ((VmInstanceMessage) msg).getVmInstanceUuid(); + return vmUuid != null ? Collections.singletonList(vmUuid) : Collections.emptyList(); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/MetadataCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/metadata/MetadataCascadeExtension.java new file mode 100644 index 00000000000..29c3c82054a --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/MetadataCascadeExtension.java @@ -0,0 +1,127 @@ +package org.zstack.compute.vm.metadata; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.core.cascade.AbstractAsyncCascadeExtension; +import org.zstack.core.cascade.CascadeAction; +import org.zstack.core.cascade.CascadeConstant; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.header.core.Completion; +import org.zstack.header.volume.VolumeDeletionStruct; +import org.zstack.header.volume.VolumeVO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 监听 Volume 级联删除事件,为受影响的 VM 触发元数据标脏。 + * + *

设计背景

+ *

{@code @MetadataImpact} 注解仅标注在 {@code APIMessage} 子类上, + * 通过 {@link VmMetadataUpdateInterceptor} 自动触发标脏。 + * 但系统中存在不经过 API 拦截器的级联删除操作也会修改 VM 存储拓扑, + * 例如:删除 PrimaryStorage → 级联删除 Volume → VM 失去数据卷。 + * 本扩展在级联清理阶段({@code DELETION_CLEANUP_CODE})捕获这些事件, + * 为受影响的 VM 调用 markDirty。

+ * + *

Cascade 图位置

+ *
+ *   ... → PrimaryStorageVO → VolumeVO → VmInstanceMetadata (本扩展)
+ *                                     → VolumeSnapshotVO → ...
+ * 
+ * + *

两道防线

+ *
    + *
  1. 本扩展 + {@code @MetadataImpact} 拦截器覆盖大部分场景
  2. + *
  3. 健康巡检兜底:周期全量比对 DB vs 存储元数据
  4. + *
+ */ +public class MetadataCascadeExtension extends AbstractAsyncCascadeExtension { + private static final CLogger logger = Utils.getLogger(MetadataCascadeExtension.class); + + private static final String NAME = "VmInstanceMetadata"; + + @Autowired + private VmMetadataDirtyMarker dirtyMarker; + + @Autowired + private DatabaseFacade dbf; + + @Override + public void asyncCascade(CascadeAction action, Completion completion) { + if (!action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) { + completion.success(); + return; + } + + if (!VmGlobalConfig.VM_METADATA.value(Boolean.class)) { + completion.success(); + return; + } + + List vmUuids = extractAffectedVmUuids(action); + if (vmUuids.isEmpty()) { + completion.success(); + return; + } + + for (String vmUuid : vmUuids) { + // 检查 VM 是否仍然存在(级联删除 VM 时不需要更新元数据) + if (dbf.isExist(vmUuid, VmInstanceVO.class)) { + logger.debug(String.format("[MetadataCascade] volume cascade cleanup affected " + + "vm[uuid:%s], marking dirty for metadata update", vmUuid)); + // 级联删除 Volume 属于存储结构变更(STORAGE → OP type 2) + dirtyMarker.markDirty(vmUuid, true); + } + } + + completion.success(); + } + + /** + * 从 CascadeAction 上下文中提取受影响的 VM UUID 列表。 + * + *

当前支持的 parentIssuer:

+ *
    + *
  • {@code VolumeVO} → 从 {@link VolumeDeletionStruct} 中获取 vmInstanceUuid
  • + *
+ */ + private List extractAffectedVmUuids(CascadeAction action) { + if (VolumeVO.class.getSimpleName().equals(action.getParentIssuer())) { + List structs = action.getParentIssuerContext(); + if (structs == null || structs.isEmpty()) { + return Collections.emptyList(); + } + + return structs.stream() + .map(s -> s.getInventory().getVmInstanceUuid()) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + } + + return Collections.emptyList(); + } + + @Override + public List getEdgeNames() { + return Arrays.asList(VolumeVO.class.getSimpleName()); + } + + @Override + public String getCascadeResourceName() { + return NAME; + } + + @Override + public CascadeAction createActionForChildResource(CascadeAction action) { + // 叶子节点,不向下传播级联 + return null; + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/ReflectionBasedVmUuidFromApiResolver.java b/compute/src/main/java/org/zstack/compute/vm/metadata/ReflectionBasedVmUuidFromApiResolver.java new file mode 100644 index 00000000000..edf07e49a1c --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/ReflectionBasedVmUuidFromApiResolver.java @@ -0,0 +1,59 @@ +package org.zstack.compute.vm.metadata; + +import org.zstack.header.message.APIMessage; +import org.zstack.header.vm.VmUuidFromApiResolver; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +/** + * 反射兜底解析器:通过反射调用 API 消息的 getVmInstanceUuid() / getResourceUuid() 方法。 + * + *

此解析器作为所有其他 Resolver 的兜底,处理未被显式 Resolver 覆盖但仍然携带 + * vmInstanceUuid 或 resourceUuid 的 API 消息。

+ * + *

注册顺序必须排在所有显式 Resolver 之后(在 XML 中排最后)。

+ */ +public class ReflectionBasedVmUuidFromApiResolver implements VmUuidFromApiResolver { + private static final CLogger logger = Utils.getLogger(ReflectionBasedVmUuidFromApiResolver.class); + + @Override + public boolean supports(APIMessage msg) { + // 兜底:对所有消息返回 true,但 resolveVmUuids 可能返回空 + return true; + } + + @Override + public List resolveVmUuids(APIMessage msg) { + // 优先尝试 getVmInstanceUuid() + String vmUuid = invokeGetter(msg, "getVmInstanceUuid"); + if (vmUuid != null) { + return Collections.singletonList(vmUuid); + } + + // fallback: getResourceUuid() + String resourceUuid = invokeGetter(msg, "getResourceUuid"); + if (resourceUuid != null) { + return Collections.singletonList(resourceUuid); + } + + logger.debug(String.format("cannot extract vmInstanceUuid from %s via reflection", msg.getClass().getName())); + return Collections.emptyList(); + } + + private String invokeGetter(Object obj, String methodName) { + try { + Method method = obj.getClass().getMethod(methodName); + return (String) method.invoke(obj); + } catch (NoSuchMethodException e) { + return null; + } catch (Exception e) { + logger.warn(String.format("failed to invoke %s on %s: %s", + methodName, obj.getClass().getName(), e.getMessage())); + return null; + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/ResourceBasedVmUuidFromApiResolver.java b/compute/src/main/java/org/zstack/compute/vm/metadata/ResourceBasedVmUuidFromApiResolver.java new file mode 100644 index 00000000000..1100dd09033 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/ResourceBasedVmUuidFromApiResolver.java @@ -0,0 +1,108 @@ +package org.zstack.compute.vm.metadata; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.SQL; +import org.zstack.header.message.APIMessage; +import org.zstack.header.tag.APIAbstractCreateTagMsg; +import org.zstack.header.tag.APIDeleteTagMsg; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.vm.VmUuidFromApiResolver; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Collections; +import java.util.List; + +/** + * 资源关联 VM UUID 解析器:从 SystemTag / ResourceConfig 类 API 消息中解析出关联的 vmInstanceUuid。 + * + *

通过 resourceType + resourceUuid 判断资源所属 VM:

+ *
    + *
  • resourceType=VmInstanceVO → 直接返回 resourceUuid
  • + *
  • resourceType=VolumeVO → 查询 VolumeVO.vmInstanceUuid
  • + *
  • resourceType=VmNicVO → 查询 VmNicVO.vmInstanceUuid
  • + *
  • resourceType=VolumeSnapshotVO → VolumeSnapshotVO.volumeUuid → VolumeVO.vmInstanceUuid
  • + *
  • 其他类型 → 不影响 VM 元数据,返回空
  • + *
+ * + *

注意

+ *

APIDeleteTagMsg 需要先查询 Tag 获取 resourceType/resourceUuid, + * 因此必须在 API 执行前(beforeDeliveryMessage)调用。

+ */ +public class ResourceBasedVmUuidFromApiResolver implements VmUuidFromApiResolver { + private static final CLogger logger = Utils.getLogger(ResourceBasedVmUuidFromApiResolver.class); + + @Autowired + private DatabaseFacade dbf; + + @Override + public boolean supports(APIMessage msg) { + return msg instanceof APIAbstractCreateTagMsg + || msg instanceof APIDeleteTagMsg; + // TODO: 扩展支持 APIUpdateResourceConfigMsg / APIDeleteResourceConfigMsg + } + + @Override + public List resolveVmUuids(APIMessage msg) { + String resourceType = null; + String resourceUuid = null; + + if (msg instanceof APIAbstractCreateTagMsg) { + resourceType = ((APIAbstractCreateTagMsg) msg).getResourceType(); + resourceUuid = ((APIAbstractCreateTagMsg) msg).getResourceUuid(); + } else if (msg instanceof APIDeleteTagMsg) { + // 查询 Tag 获取 resourceType 和 resourceUuid + SystemTagVO tag = dbf.findByUuid(((APIDeleteTagMsg) msg).getUuid(), SystemTagVO.class); + if (tag != null) { + resourceType = tag.getResourceType(); + resourceUuid = tag.getResourceUuid(); + } + } + + if (resourceType == null || resourceUuid == null) { + return Collections.emptyList(); + } + + return resolveByResourceType(resourceType, resourceUuid); + } + + private List resolveByResourceType(String resourceType, String resourceUuid) { + // 直接关联 VM + if ("VmInstanceVO".equals(resourceType)) { + return Collections.singletonList(resourceUuid); + } + + // Volume → VM + if ("VolumeVO".equals(resourceType)) { + return SQL.New( + "SELECT v.vmInstanceUuid FROM VolumeVO v " + + "WHERE v.uuid = :uuid AND v.vmInstanceUuid IS NOT NULL", + String.class + ).param("uuid", resourceUuid).list(); + } + + // VmNic → VM + if ("VmNicVO".equals(resourceType)) { + return SQL.New( + "SELECT n.vmInstanceUuid FROM VmNicVO n " + + "WHERE n.uuid = :uuid AND n.vmInstanceUuid IS NOT NULL", + String.class + ).param("uuid", resourceUuid).list(); + } + + // VolumeSnapshot → Volume → VM + if ("VolumeSnapshotVO".equals(resourceType)) { + return SQL.New( + "SELECT v.vmInstanceUuid FROM VolumeVO v " + + "WHERE v.uuid = (SELECT s.volumeUuid FROM VolumeSnapshotVO s WHERE s.uuid = :uuid) " + + "AND v.vmInstanceUuid IS NOT NULL", + String.class + ).param("uuid", resourceUuid).list(); + } + + // 其他资源类型不影响 VM 元数据 + logger.trace(String.format("resourceType[%s] does not map to VM metadata, skipping", resourceType)); + return Collections.emptyList(); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataBuilder.java b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataBuilder.java new file mode 100644 index 00000000000..006fc13c486 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataBuilder.java @@ -0,0 +1,145 @@ +package org.zstack.compute.vm.metadata; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO_; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.tag.SystemTagVO; +import org.zstack.header.tag.SystemTagVO_; +import org.zstack.header.vm.VmInstanceMetadataDTO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.VmNicVO; +import org.zstack.header.vm.VmNicVO_; +import org.zstack.header.volume.VolumeVO; +import org.zstack.header.volume.VolumeVO_; +import org.zstack.resourceconfig.ResourceConfigVO; +import org.zstack.resourceconfig.ResourceConfigVO_; +import org.zstack.utils.gson.JSONObjectUtil; +import org.zstack.utils.logging.CLogger; +import org.zstack.utils.Utils; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 构建虚拟机元数据 payload 的 Spring Component。 + * + *

从 VmInstanceBase 中提取出来,以获得 Spring AOP 代理的 {@code @Transactional} 支持。 + * VmInstanceBase 实例不是 Spring 单例 Bean,其内部方法调用不经过 AOP 代理, + * 因此 {@code @Transactional} 注解在 VmInstanceBase 自身方法上不生效。

+ * + *

{@link #buildVmInstanceMetadata(String)} 执行 6+ 条 SELECT 查询, + * 必须在同一个 REPEATABLE READ 事务快照内完成,以保证读一致性。

+ * + * @see VmInstanceMetadataDTO + */ +public class VmMetadataBuilder { + private static final CLogger logger = Utils.getLogger(VmMetadataBuilder.class); + + /** Payload 大小预警阈值(8 MB) */ + public static final int WARN_THRESHOLD = 8 * 1024 * 1024; + + /** Payload 大小拒绝阈值(30 MB) */ + public static final int REJECT_THRESHOLD = 30 * 1024 * 1024; + + @Autowired + private DatabaseFacade dbf; + + /** + * 从 DB 全量构建指定 VM 的元数据 JSON 字符串。 + * + *

使用 {@code @Transactional(readOnly = true)} 确保所有 SELECT 查询 + * 在同一个 InnoDB REPEATABLE READ 事务快照内执行,保证读一致性。

+ * + * @param vmInstanceUuid 目标虚拟机 UUID + * @return 元数据 JSON 字符串 + */ + @Transactional(readOnly = true) + public String buildVmInstanceMetadata(String vmInstanceUuid) { + VmInstanceMetadataDTO dto = new VmInstanceMetadataDTO(); + + // ── VM 本体 ── + VmInstanceVO vm = Q.New(VmInstanceVO.class).eq(VmInstanceVO_.uuid, vmInstanceUuid).find(); + dto.vm = buildResourceMetadata(vm.getUuid(), vm); + + // ── 云盘(挂载的 + 已卸载但 lastVmInstanceUuid 指向本 VM 的) ── + List volumes = new ArrayList<>(); + volumes.addAll(Q.New(VolumeVO.class).eq(VolumeVO_.vmInstanceUuid, vmInstanceUuid).list()); + volumes.addAll(Q.New(VolumeVO.class).isNull(VolumeVO_.vmInstanceUuid) + .eq(VolumeVO_.lastVmInstanceUuid, vmInstanceUuid).list()); + volumes.forEach(v -> dto.volumes.add(buildResourceMetadata(v.getUuid(), v))); + + // ── 网卡 ── + List nics = Q.New(VmNicVO.class).eq(VmNicVO_.vmInstanceUuid, vmInstanceUuid).list(); + nics.forEach(n -> dto.nics.add(buildResourceMetadata(n.getUuid(), n))); + + // ── 快照 ── + List volumeUuids = volumes.stream().map(VolumeVO::getUuid).collect(Collectors.toList()); + if (!volumeUuids.isEmpty()) { + Q.New(VolumeSnapshotVO.class).in(VolumeSnapshotVO_.volumeUuid, volumeUuids).list() + .forEach(s -> dto.snapshots + .computeIfAbsent(s.getVolumeUuid(), k -> new ArrayList<>()) + .add(JSONObjectUtil.toJsonString(s))); + } + + // ── 快照组 ── + List groups = Q.New(VolumeSnapshotGroupVO.class) + .eq(VolumeSnapshotGroupVO_.vmInstanceUuid, vmInstanceUuid).list(); + dto.snapshotGroups = groups.stream() + .map(JSONObjectUtil::toJsonString).collect(Collectors.toList()); + + List groupUuids = groups.stream() + .map(VolumeSnapshotGroupVO::getUuid).collect(Collectors.toList()); + if (!groupUuids.isEmpty()) { + dto.snapshotGroupRefs = Q.New(VolumeSnapshotGroupRefVO.class) + .in(VolumeSnapshotGroupRefVO_.volumeSnapshotGroupUuid, groupUuids).list() + .stream().map(JSONObjectUtil::toJsonString).collect(Collectors.toList()); + } + + return JSONObjectUtil.toJsonString(dto); + } + + /** + * 构建单个资源的 {@link VmInstanceMetadataDTO.ResourceMetadata}。 + * + *

VO 全量 JSON 明文存储;SystemTagVO 和 ResourceConfigVO 整体列表序列化为 JSON 数组后 + * 一次性 Base64 编码,以保护可能包含的密码、密钥等敏感信息。

+ * + * @param resourceUuid 资源 UUID + * @param vo 资源 VO 对象(VmInstanceVO / VolumeVO / VmNicVO) + * @return 填充完毕的 ResourceMetadata + */ + private VmInstanceMetadataDTO.ResourceMetadata buildResourceMetadata(String resourceUuid, Object vo) { + VmInstanceMetadataDTO.ResourceMetadata meta = new VmInstanceMetadataDTO.ResourceMetadata(); + meta.resourceUuid = resourceUuid; + meta.vo = JSONObjectUtil.toJsonString(vo); + + // SystemTagVO: 全部 → JSON 数组 → Base64 + List tagVOs = Q.New(SystemTagVO.class) + .eq(SystemTagVO_.resourceUuid, resourceUuid).list(); + List tagJsons = tagVOs.stream() + .map(JSONObjectUtil::toJsonString).collect(Collectors.toList()); + meta.systemTags = Base64.getEncoder().encodeToString( + JSONObjectUtil.toJsonString(tagJsons).getBytes(StandardCharsets.UTF_8)); + + // ResourceConfigVO: 全部 → JSON 数组 → Base64 + List cfgVOs = Q.New(ResourceConfigVO.class) + .eq(ResourceConfigVO_.resourceUuid, resourceUuid).list(); + List cfgJsons = cfgVOs.stream() + .map(JSONObjectUtil::toJsonString).collect(Collectors.toList()); + meta.resourceConfigs = Base64.getEncoder().encodeToString( + JSONObjectUtil.toJsonString(cfgJsons).getBytes(StandardCharsets.UTF_8)); + + return meta; + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDirtyMarker.java b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDirtyMarker.java new file mode 100644 index 00000000000..fba375c2bdc --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataDirtyMarker.java @@ -0,0 +1,589 @@ +package org.zstack.compute.vm.metadata; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.core.Platform; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.thread.ChainTask; +import org.zstack.core.thread.PeriodicTask; +import org.zstack.core.thread.SyncTaskChain; +import org.zstack.core.thread.ThreadFacade; +import org.zstack.header.Component; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.managementnode.ManagementNodeChangeListener; +import org.zstack.header.managementnode.ManagementNodeInventory; +import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint; +import org.zstack.header.message.MessageReply; +import org.zstack.header.vm.UpdateVmInstanceMetadataMsg; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmMetadataDirtyVO; +import org.zstack.header.vm.VmMetadataDirtyVO_; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * VM 元数据 Dirty Mark + Poller 机制的核心实现。 + * + *

职责

+ *
    + *
  • {@link #markDirty(String)} — 标脏入口,INSERT ON DUPLICATE KEY UPDATE + 立即唤醒
  • + *
  • {@link MetadataDirtyPoller} — 周期轮询安全网,处理退避到期行、MN 宕机释放行等
  • + *
  • {@link #claimAndFlush()} — CAS 认领 + 提交刷写
  • + *
  • {@link #doFlush} — 构建 payload → 发送 UpdateVmInstanceMetadataMsg → 成功/失败处理
  • + *
  • {@link ManagementNodeChangeListener#nodeLeft} — MN 宕机后立即接管
  • + *
+ * + *

串行化保证(三层)

+ *
+ *   Layer 1 — DB CAS 认领:UPDATE WHERE managementNodeUuid IS NULL → 同一行只被一个 MN 处理
+ *   Layer 2 — ChainTask 队列 "update-vm-{vmUuid}-metadata":syncLevel=1, maxPending=1
+ *   Layer 3 — 主存储级队列 "update-metadata-on-ps-{psUuid}"(在 PS handler 内部实现)
+ * 
+ * + * @see VmMetadataDirtyVO + * @see VmMetadataUpdateInterceptor + */ +public class VmMetadataDirtyMarker implements Component, ManagementNodeChangeListener, ManagementNodeReadyExtensionPoint { + private static final CLogger logger = Utils.getLogger(VmMetadataDirtyMarker.class); + + // ===================================================================== + // 常量 + // ===================================================================== + + /** 指数退避基准延迟(秒) */ + private static final long BASE_DELAY_SECONDS = 10; + + /** 指数退避的指数上限,防止左移溢出。2^10 = 1024,最大延迟 = 10 * 1024 = 10240s ≈ 2.8h */ + private static final int MAX_EXPONENT = 10; + + // ===================================================================== + // 注入 + // ===================================================================== + + @Autowired + private CloudBus bus; + + @Autowired + private DatabaseFacade dbf; + + @Autowired + private ThreadFacade thdf; + + // ===================================================================== + // Poller 状态 + // ===================================================================== + + private Future pollerFuture; + + // ===================================================================== + // Component 生命周期 + // ===================================================================== + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + stopPoller(); + return true; + } + + // ===================================================================== + // ManagementNodeReadyExtensionPoint:MN 就绪后启动 Poller + // ===================================================================== + + @Override + public void managementNodeReady() { + startPoller(); + + VmGlobalConfig.VM_METADATA_DIRTY_POLL_INTERVAL.installUpdateExtension((oldValue, newValue) -> { + restartPoller(); + }); + } + + // ===================================================================== + // ManagementNodeChangeListener:MN 拓扑变化处理 + // ===================================================================== + + @Override + public void nodeLeft(ManagementNodeInventory inv) { + // MN 宕机 → FK SET_NULL 已释放其认领的 dirty 行 + // 异步触发一轮 Poller,避免阻塞心跳检测回调线程(P2-2.3 修复) + logger.info(String.format("[MetadataDirty] node[%s] left, scheduling immediate claim and flush", + inv.getUuid())); + thdf.submit(new org.zstack.core.thread.Task() { + @Override + public String getName() { + return "metadata-dirty-node-left-claim"; + } + + @Override + public Void call() { + claimAndFlush(); + return null; + } + }); + } + + @Override + public void nodeJoin(ManagementNodeInventory inv) { + // 无需特殊处理,新 MN 的 Poller 正常启动即可 + } + + @Override + public void iAmDead(ManagementNodeInventory inv) { + // 本 MN 即将死亡,不做处理 + // FK SET_NULL 会自动释放本 MN 认领的行 + } + + @Override + public void iJoin(ManagementNodeInventory inv) { + // 由 managementNodeReady 启动 Poller + } + + // ===================================================================== + // markDirty — 标脏入口(公开方法) + // ===================================================================== + + /** + * 将指定 VM 标记为"元数据脏",需要重新写入主存储。 + * + *

使用 INSERT ON DUPLICATE KEY UPDATE,保证:

+ *
    + *
  • 行不存在 → INSERT(dirtyVersion=1)
  • + *
  • 行已存在 → dirtyVersion +1(标记"有新变更")
  • + *
  • storageStructureChange 使用 OR 升级策略:一旦标记为 STORAGE,在本轮 dirty 生命周期内不会降级为 CONFIG
  • + *
  • 不重置 retryCount(保留退避状态)
  • + *
  • 不修改 managementNodeUuid(不干扰正在执行的刷写)
  • + *
  • 不修改 nextRetryTime(不干扰退避计时)
  • + *
+ * + *

markDirty 后立即调用 {@link #triggerFlushForVm(String)}, + * 尝试认领并提交刷写,消除最长 N 秒的 Poller 等待延迟。

+ * + * @param vmInstanceUuid 目标虚拟机 UUID + * @param storageStructureChange 是否涉及存储结构变更({@code @MetadataImpact(STORAGE)} → true)。 + * 使用 OR 升级:若已有 STORAGE 标记,本次 CONFIG 不会将其降级。 + */ + public void markDirty(String vmInstanceUuid, boolean storageStructureChange) { + // 前置检查:功能开关 + if (!VmGlobalConfig.VM_METADATA.value(Boolean.class)) { + return; + } + + // INSERT ... ON DUPLICATE KEY UPDATE dirtyVersion = dirtyVersion + 1 + // storageStructureChange 使用 OR 升级:false OR true = true, true OR false = true + SQL.New("INSERT INTO VmMetadataDirtyVO (vmInstanceUuid, dirtyVersion, storageStructureChange) " + + "VALUES (:vmUuid, 1, :ssc) " + + "ON DUPLICATE KEY UPDATE dirtyVersion = dirtyVersion + 1, " + + "storageStructureChange = storageStructureChange OR VALUES(storageStructureChange)") + .param("vmUuid", vmInstanceUuid) + .param("ssc", storageStructureChange) + .execute(); + + logger.debug(String.format("[MetadataDirty] marked dirty for vm[uuid:%s], storageStructureChange=%s", + vmInstanceUuid, storageStructureChange)); + + // 立即唤醒:尝试认领并提交刷写,不等待 Poller 轮询 + triggerFlushForVm(vmInstanceUuid); + } + + /** + * 标脏入口(便捷重载,默认 storageStructureChange=false,即 CONFIG 级别)。 + * + *

适用于非 API 触发的调用方(如升级全量刷新等),不涉及存储拓扑变更。 + * 涉及存储拓扑变更的场景(级联删除 Volume、快照清理等)应明确调用 + * {@link #markDirty(String, boolean)} 并传入 {@code true}。

+ * + * @param vmInstanceUuid 目标虚拟机 UUID + */ + public void markDirty(String vmInstanceUuid) { + markDirty(vmInstanceUuid, false); + } + + // ===================================================================== + // triggerFlushForVm — 立即唤醒(单 VM) + // ===================================================================== + + /** + * 立即尝试认领并刷写指定 VM 的 dirty 行。 + * 若行已被认领或处于退避期,跳过(Poller 安全网会处理)。 + */ + private void triggerFlushForVm(String vmUuid) { + int claimed = SQL.New("UPDATE VmMetadataDirtyVO " + + "SET managementNodeUuid = :myId " + + "WHERE vmInstanceUuid = :vmUuid " + + "AND managementNodeUuid IS NULL " + + "AND (nextRetryTime IS NULL OR nextRetryTime <= CURRENT_TIMESTAMP)") + .param("myId", Platform.getManagementServerId()) + .param("vmUuid", vmUuid) + .execute(); + + if (claimed == 0) { + return; // 已被认领 or 退避中 → Poller 处理 + } + + VmMetadataDirtyVO dirty = dbf.findByUuid(vmUuid, VmMetadataDirtyVO.class); + if (dirty == null) { + return; + } + + submitFlushTask(dirty); + } + + // ===================================================================== + // Poller — 轮询安全网 + // ===================================================================== + + /** + * 内部 PeriodicTask 实现。 + * + *

Poller 角色定位:markDirty 后的 triggerFlushForVm 已覆盖常规场景。 + * Poller 降级为安全网,负责处理: + *

    + *
  • 退避中的行(nextRetryTime 到期后才能认领)
  • + *
  • MN 宕机后 FK SET_NULL 释放的孤儿行
  • + *
  • triggerFlushForVm 认领失败的行(已被其他 MN Poller 认领)
  • + *
+ */ + private class MetadataDirtyPoller implements PeriodicTask { + @Override + public TimeUnit getTimeUnit() { + return TimeUnit.SECONDS; + } + + @Override + public long getInterval() { + return VmGlobalConfig.VM_METADATA_DIRTY_POLL_INTERVAL.value(Long.class); + } + + @Override + public String getName() { + return "vm-metadata-dirty-poller"; + } + + @Override + public void run() { + claimAndFlush(); + } + } + + private synchronized void startPoller() { + if (pollerFuture != null) { + pollerFuture.cancel(false); + } + pollerFuture = thdf.submitPeriodicTask(new MetadataDirtyPoller()); + logger.info("[MetadataDirty] poller started"); + } + + private synchronized void stopPoller() { + if (pollerFuture != null) { + pollerFuture.cancel(false); + pollerFuture = null; + logger.info("[MetadataDirty] poller stopped"); + } + } + + private void restartPoller() { + logger.info("[MetadataDirty] restarting poller due to config change"); + startPoller(); + } + + // ===================================================================== + // claimAndFlush — 认领 + 提交刷写(Poller 和 nodeLeft 共用) + // ===================================================================== + + /** + * CAS 认领一批 dirty 行并提交刷写。 + */ + private void claimAndFlush() { + // 功能关闭时跳过,避免 Poller 空转(P2-2.2 修复) + if (!VmGlobalConfig.VM_METADATA.value(Boolean.class)) { + return; + } + + List claimed = claimDirtyRows(); + for (VmMetadataDirtyVO dirty : claimed) { + submitFlushTask(dirty); + } + } + + /** + * CAS 原子认领一批 dirty 行。 + * + *

单条 UPDATE 天然原子,无锁等待,无死锁风险。

+ * + * @return 认领到的 dirty 行列表 + */ + private List claimDirtyRows() { + // Step 1: CAS 原子认领 + int claimed = SQL.New("UPDATE VmMetadataDirtyVO " + + "SET managementNodeUuid = :myId " + + "WHERE managementNodeUuid IS NULL " + + "AND (nextRetryTime IS NULL OR nextRetryTime <= CURRENT_TIMESTAMP) " + + "ORDER BY lastOpDate ASC " + + "LIMIT :batchSize") + .param("myId", Platform.getManagementServerId()) + .param("batchSize", VmGlobalConfig.VM_METADATA_DIRTY_BATCH_SIZE.value(Integer.class)) + .execute(); + + if (claimed == 0) { + return Collections.emptyList(); + } + + // Step 2: 查询刚认领到的行 + return Q.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.managementNodeUuid, Platform.getManagementServerId()) + .list(); + } + + // ===================================================================== + // submitFlushTask — 嵌套 ChainTask 提交(全局限流 + per-VM 串行去重) + // ===================================================================== + + /** + * 将 dirty 行的刷写任务提交到嵌套 ChainTask 队列。 + * + *

外层全局限流 + 内层 per-VM 串行 + 去重。

+ */ + private void submitFlushTask(VmMetadataDirtyVO dirty) { + final String vmUuid = dirty.getVmInstanceUuid(); + + // 外层全局限流 + thdf.chainSubmit(new ChainTask(null) { + @Override + public String getSyncSignature() { + return "update-vm-metadata-global"; + } + + @Override + public int getSyncLevel() { + return VmGlobalConfig.VM_METADATA_GLOBAL_MAX_CONCURRENT.value(Integer.class); + } + + @Override + public void run(final SyncTaskChain outerChain) { + // 内层 per-VM 串行 + 去重 + thdf.chainSubmit(new ChainTask(null) { + @Override + public String getSyncSignature() { + return String.format("update-vm-%s-metadata", vmUuid); + } + + @Override + public int getSyncLevel() { + return 1; + } + + @Override + protected int getMaxPendingTasks() { + return 1; + } + + @Override + protected String getDeduplicateString() { + return getSyncSignature(); + } + + @Override + protected void exceedMaxPendingCallback() { + // 已有 running + pending,本次多余。 + // 不释放认领——该行可能正在被当前 MN 的 running task 处理, + // 强行 releaseClaim 会导致其他 MN 并发认领同一 VM(P0-0.2 修复)。 + // 直接推进外层 chain,running task 完成后自然释放。 + logger.debug(String.format("[MetadataDirty] vm[uuid:%s] queue already has " + + "pending task, skipping (claim retained by running task)", vmUuid)); + outerChain.next(); + } + + @Override + public void run(final SyncTaskChain innerChain) { + doFlush(dirty, () -> { + innerChain.next(); + outerChain.next(); + }); + } + + @Override + public String getName() { + return String.format("update-vm-%s-metadata-task", vmUuid); + } + }); + } + + @Override + public String getName() { + return String.format("update-vm-%s-metadata-global-task", vmUuid); + } + }); + } + + // ===================================================================== + // doFlush — 核心刷写逻辑 + // ===================================================================== + + /** + * 执行元数据刷写。 + * + *

流程:

+ *
    + *
  1. 快照 dirtyVersion
  2. + *
  3. 前置检查(VM 是否存在)
  4. + *
  5. 发送 UpdateVmInstanceMetadataMsg(由 VmInstanceBase 构建 payload 并写入主存储)
  6. + *
  7. 成功 → onFlushSuccess(条件删除 dirty 行)
  8. + *
  9. 失败 → onFlushFailure(指数退避或放弃)
  10. + *
+ */ + private void doFlush(VmMetadataDirtyVO dirty, Runnable chainNext) { + String vmUuid = dirty.getVmInstanceUuid(); + + // 0. 记录刷写开始时的 dirtyVersion 快照 + long snapshotVersion = dirty.getDirtyVersion(); + + // 1. 前置检查:VM 是否存在 + if (!dbf.isExist(vmUuid, VmInstanceVO.class)) { + // VM 已删除,FK CASCADE 应已删除 dirty 行,兜底删除 + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .delete(); + chainNext.run(); + return; + } + + // 2. 发送到 VmInstanceBase 处理(由 VmInstanceBase 内部构建 payload 并写入主存储) + UpdateVmInstanceMetadataMsg msg = new UpdateVmInstanceMetadataMsg(); + msg.setUuid(vmUuid); + msg.setStorageStructureChange(dirty.isStorageStructureChange()); + msg.setTimeout(TimeUnit.MINUTES.toMillis(2)); + bus.makeLocalServiceId(msg, VmInstanceConstant.SERVICE_ID); + + bus.send(msg, new CloudBusCallBack(null) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + onFlushSuccess(vmUuid, snapshotVersion); + } else { + onFlushFailure(vmUuid, reply.getError()); + } + chainNext.run(); + } + }); + } + + // ===================================================================== + // onFlushSuccess — 刷写成功处理(dirtyVersion 条件删除) + // ===================================================================== + + /** + * 刷写成功后的处理。 + * + *

条件删除:仅当 dirtyVersion == snapshotVersion 时删除, + * 即"刷写期间没有新的 markDirty 到来"。

+ * + *

如果 dirtyVersion > snapshotVersion,说明刷写期间有新变更, + * 释放认领让 triggerFlush / Poller 重新处理。

+ */ + private void onFlushSuccess(String vmUuid, long snapshotVersion) { + int deleted = SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .eq(VmMetadataDirtyVO_.dirtyVersion, snapshotVersion) + .delete(); + + if (deleted == 0) { + // dirtyVersion > snapshotVersion → 刷写期间有新变更 + // 释放认领,让 triggerFlush / Poller 重新处理 + // 同时重置 retryCount(本次成功说明通路正常) + // 重置 storageStructureChange=false(本轮 STORAGE 变更已成功刷写, + // 后续新变更的 OR 升级会在 markDirty 中重新标记) + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) + .set(VmMetadataDirtyVO_.retryCount, 0) + .set(VmMetadataDirtyVO_.nextRetryTime, null) + .set(VmMetadataDirtyVO_.storageStructureChange, false) + .update(); + + logger.debug(String.format("[MetadataDirty] vm[uuid:%s] has new changes during flush " + + "(snapshotVersion=%d), released for re-processing", vmUuid, snapshotVersion)); + } else { + logger.debug(String.format("[MetadataDirty] vm[uuid:%s] flush completed and dirty row removed", + vmUuid)); + } + } + + // ===================================================================== + // onFlushFailure — 刷写失败处理(指数退避 / 放弃) + // ===================================================================== + + /** + * 刷写失败后的处理。 + * + *

retryCount++ → 达到上限则告警 + 删除行(下次 API 自动重试); + * 未达上限则释放认领 + 指数退避。

+ */ + private void onFlushFailure(String vmUuid, ErrorCode error) { + VmMetadataDirtyVO dirty = dbf.findByUuid(vmUuid, VmMetadataDirtyVO.class); + if (dirty == null) { + return; // VM 已销毁,FK CASCADE 已清理 + } + + int newRetryCount = dirty.getRetryCount() + 1; + int maxRetry = VmGlobalConfig.VM_METADATA_MAX_RETRY.value(Integer.class); + + if (newRetryCount >= maxRetry) { + // 达到上限 → 告警 + 删除行 + // 下次该 VM 的 @MetadataImpact API 成功时会重新 markDirty,自然重试 + logger.error(String.format("[MetadataDirty] metadata update for vm[uuid:%s] failed " + + "after %d retries, giving up. Will auto-retry on next API that modifies this VM. " + + "Error: %s", vmUuid, newRetryCount, error)); + + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .delete(); + return; + } + + // 未达上限 → 释放认领 + 指数退避 + long delaySec = BASE_DELAY_SECONDS * (1L << Math.min(newRetryCount, MAX_EXPONENT)); + Timestamp nextRetry = Timestamp.from(Instant.now().plusSeconds(delaySec)); + + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) + .set(VmMetadataDirtyVO_.retryCount, newRetryCount) + .set(VmMetadataDirtyVO_.nextRetryTime, nextRetry) + .update(); + + logger.warn(String.format("[MetadataDirty] metadata update for vm[uuid:%s] failed " + + "(retry %d/%d), next retry at %s. Error: %s", + vmUuid, newRetryCount, maxRetry, nextRetry, error)); + } + + // ===================================================================== + // 辅助方法 + // ===================================================================== + + /** + * 释放 dirty 行的认领(managementNodeUuid 置 NULL)。 + */ + private void releaseClaim(String vmUuid) { + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) + .update(); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java new file mode 100644 index 00000000000..57ce2628c87 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/VmMetadataUpdateInterceptor.java @@ -0,0 +1,206 @@ +package org.zstack.compute.vm.metadata; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.header.Component; +import org.zstack.header.message.*; +import org.zstack.header.vm.MetadataImpact; +import org.zstack.header.vm.VmUuidFromApiResolver; +import org.zstack.utils.BeanUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 拦截标注了 {@link MetadataImpact} 的 API 消息,在 API 成功后调用 markDirty 触发元数据更新。 + * + *

工作流程

+ *
+ * API Message 投递                          Event 发布
+ *       │                                      │
+ *       ▼                                      ▼
+ * BeforeDeliveryMessageInterceptor    BeforePublishEventInterceptor
+ *       │                                      │
+ *       │  检测 @MetadataImpact               │  通过 apiId 匹配
+ *       │  通过 VmUuidFromApiResolver          │  检查 API 是否成功
+ *       │  解析 vmUuid 列表                    │  调用 markDirty()
+ *       │  缓存到 pendingApis                  │  清理 pendingApis
+ *       │  (key = apiId)                       │
+ *       ▼                                      ▼
+ * 
+ * + *

VM UUID 解析链

+ *

通过注入的 {@link VmUuidFromApiResolver} 列表按顺序解析 vmUuid:

+ *
    + *
  1. {@link DefaultVmUuidFromApiResolver} — VmInstanceMessage 接口
  2. + *
  3. {@link VolumeBasedVmUuidFromApiResolver} — VolumeMessage → 查库
  4. + *
  5. {@link ResourceBasedVmUuidFromApiResolver} — Tag/Config API → resourceType 查库
  6. + *
  7. {@link ReflectionBasedVmUuidFromApiResolver} — 反射兜底
  8. + *
+ * + *

标脏策略

+ *

使用 {@link VmMetadataDirtyMarker#markDirty(String, boolean)} 将 VM 标记为脏, + * INSERT ON DUPLICATE KEY UPDATE 天然去重,100 个 API 只产生 1 行 dirty 行。 + * markDirty 后立即尝试认领并刷写,Poller 作为安全网处理退避和异常场景。

+ */ +public class VmMetadataUpdateInterceptor implements Component { + private static final CLogger logger = Utils.getLogger(VmMetadataUpdateInterceptor.class); + + @Autowired + private CloudBus bus; + + @Autowired + private VmMetadataDirtyMarker dirtyMarker; + + /** + * VM UUID 解析器链,按注册顺序尝试(在 VmInstanceManager.xml 中注册)。 + */ + @Autowired(required = false) + private List resolvers = Collections.emptyList(); + + // apiId -> MetadataImpactInfo 映射,在 API 投递时写入,在 Event 发布时消费 + private final Map pendingApis = new ConcurrentHashMap<>(); + + // 标注了 @MetadataImpact 的 API 消息类集合 + private final Set> impactApiClasses = ConcurrentHashMap.newKeySet(); + + @Override + public boolean start() { + // 1. 扫描所有标注了 @MetadataImpact 的 API 消息类 + scanMetadataImpactApis(); + + // 2. 注册 BeforeDeliveryMessageInterceptor,在 API 消息被投递处理前, + // 通过 Resolver 链解析 vmInstanceUuid 并缓存 + bus.installBeforeDeliveryMessageInterceptor(new AbstractBeforeDeliveryMessageInterceptor() { + @Override + public void beforeDeliveryMessage(Message msg) { + if (!(msg instanceof APIMessage)) { + return; + } + if (!impactApiClasses.contains(msg.getClass())) { + return; + } + + // vm.metadata 功能总开关,关闭时跳过所有元数据更新 + if (!VmGlobalConfig.VM_METADATA.value(Boolean.class)) { + return; + } + + APIMessage apiMsg = (APIMessage) msg; + List vmUuids = resolveVmUuids(apiMsg); + if (vmUuids.isEmpty()) { + return; + } + + MetadataImpact impact = msg.getClass().getAnnotation(MetadataImpact.class); + pendingApis.put(apiMsg.getId(), new MetadataImpactInfo( + vmUuids, impact.value(), impact.updateOnFailure())); + } + }, impactApiClasses.toArray(new Class[0])); + + // 3. 注册 BeforePublishEventInterceptor,在 Event 发布前检查并标脏 + bus.installBeforePublishEventInterceptor(new AbstractBeforePublishEventInterceptor() { + @Override + public void beforePublishEvent(Event evt) { + if (!(evt instanceof APIEvent)) { + return; + } + + APIEvent apiEvent = (APIEvent) evt; + MetadataImpactInfo info = pendingApis.remove(apiEvent.getApiId()); + if (info == null) { + return; + } + + // API 失败则跳过(除非 @MetadataImpact(updateOnFailure=true)) + if (apiEvent.getError() != null && !info.updateOnFailure) { + return; + } + + for (String vmUuid : info.vmUuids) { + submitMarkDirty(vmUuid, info.impact); + } + } + }); + + return true; + } + + private void scanMetadataImpactApis() { + // 利用 ZStack 的反射工具扫描所有带 @MetadataImpact 注解的 APIMessage 子类 + BeanUtils.reflections.getTypesAnnotatedWith(MetadataImpact.class).forEach(clz -> { + if (APIMessage.class.isAssignableFrom(clz)) { + impactApiClasses.add((Class) clz); + logger.debug(String.format("detected @MetadataImpact API: %s", clz.getName())); + } + }); + } + + /** + * 通过 Resolver 链解析 API 消息关联的 vmInstanceUuid 列表。 + * + *

按注册顺序遍历 resolvers,使用第一个 supports() 返回 true 的 Resolver 进行解析。 + * 如果所有 Resolver 都不支持或返回空列表,则返回空。

+ */ + private List resolveVmUuids(APIMessage msg) { + for (VmUuidFromApiResolver resolver : resolvers) { + if (resolver.supports(msg)) { + List vmUuids = resolver.resolveVmUuids(msg); + if (vmUuids != null && !vmUuids.isEmpty()) { + return vmUuids; + } + } + } + logger.debug(String.format("no resolver could extract vmUuids from %s", msg.getClass().getName())); + return Collections.emptyList(); + } + + /** + * 提交元数据标脏。 + * + *

根据 {@link MetadataImpact.Impact} 决定 OP type:

+ *
    + *
  • {@code CONFIG} → storageStructureChange=false(OP type 1)
  • + *
  • {@code STORAGE} → storageStructureChange=true(OP type 2)
  • + *
+ * + * @param vmInstanceUuid 目标虚拟机 UUID + * @param impact 影响级别(来自 {@code @MetadataImpact} 注解) + */ + void submitMarkDirty(String vmInstanceUuid, MetadataImpact.Impact impact) { + boolean storageStructureChange = (impact == MetadataImpact.Impact.STORAGE); + logger.debug(String.format("[MetadataDirty] API succeeded, marking dirty " + + "for vm[uuid:%s], impact=%s, storageStructureChange=%s", + vmInstanceUuid, impact, storageStructureChange)); + dirtyMarker.markDirty(vmInstanceUuid, storageStructureChange); + } + + @Override + public boolean stop() { + pendingApis.clear(); + return true; + } + + /** + * 缓存 API 投递时提取的元数据影响信息,供 Event 发布时匹配使用。 + */ + private static class MetadataImpactInfo { + final List vmUuids; + final MetadataImpact.Impact impact; + final boolean updateOnFailure; + + MetadataImpactInfo(List vmUuids, MetadataImpact.Impact impact, boolean updateOnFailure) { + this.vmUuids = vmUuids; + this.impact = impact; + this.updateOnFailure = updateOnFailure; + } + } +} \ No newline at end of file diff --git a/compute/src/main/java/org/zstack/compute/vm/metadata/VolumeBasedVmUuidFromApiResolver.java b/compute/src/main/java/org/zstack/compute/vm/metadata/VolumeBasedVmUuidFromApiResolver.java new file mode 100644 index 00000000000..b3f27a6eea9 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/metadata/VolumeBasedVmUuidFromApiResolver.java @@ -0,0 +1,66 @@ +package org.zstack.compute.vm.metadata; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.db.SQL; +import org.zstack.header.message.APIMessage; +import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.VmUuidFromApiResolver; +import org.zstack.header.volume.VolumeMessage; + +import java.util.Collections; +import java.util.List; + +/** + * Volume 关联 VM UUID 解析器:从实现 {@link VolumeMessage} 接口的 API 消息中获取 volumeUuid, + * 查询 VolumeVO 得到关联的 vmInstanceUuid。 + * + *

覆盖快照、云盘挂载/卸载等涉及 Volume 但不直接携带 vmInstanceUuid 的 API。

+ * + *

排除条件

+ *

如果消息同时实现了 {@link VmInstanceMessage},则由 {@link DefaultVmUuidFromApiResolver} 处理, + * 本解析器不参与。

+ * + *

解析时机

+ *

在 API 执行前调用。对于 attach 场景,VolumeVO.vmInstanceUuid 可能尚未设置, + * 此时通过反射 fallback 到 msg.getVmInstanceUuid()(如果存在)。

+ */ +public class VolumeBasedVmUuidFromApiResolver implements VmUuidFromApiResolver { + + @Override + public boolean supports(APIMessage msg) { + // 同时实现 VmInstanceMessage 的由 DefaultResolver 处理 + return msg instanceof VolumeMessage && !(msg instanceof VmInstanceMessage); + } + + @Override + public List resolveVmUuids(APIMessage msg) { + String volumeUuid = ((VolumeMessage) msg).getVolumeUuid(); + if (volumeUuid == null) { + return Collections.emptyList(); + } + + // 查询 Volume → vmInstanceUuid + List vmUuids = SQL.New( + "SELECT v.vmInstanceUuid FROM VolumeVO v " + + "WHERE v.uuid = :uuid AND v.vmInstanceUuid IS NOT NULL", + String.class + ).param("uuid", volumeUuid).list(); + + if (!vmUuids.isEmpty()) { + return vmUuids; + } + + // Fallback:尝试通过反射获取 msg 上的 getVmInstanceUuid() + // 适用于 APIAttachDataVolumeToVmMsg 等同时携带 vmInstanceUuid 的消息 + try { + String vmUuid = (String) msg.getClass().getMethod("getVmInstanceUuid").invoke(msg); + if (vmUuid != null) { + return Collections.singletonList(vmUuid); + } + } catch (Exception ignored) { + // 无此方法,忽略 + } + + return Collections.emptyList(); + } +} diff --git a/conf/db/upgrade/V4.10.29__schema.sql b/conf/db/upgrade/V4.10.29__schema.sql new file mode 100644 index 00000000000..32edbe3e965 --- /dev/null +++ b/conf/db/upgrade/V4.10.29__schema.sql @@ -0,0 +1,23 @@ +-- Feature: VM Metadata Dirty Mark + Poller (replaces GC-based approach) + +CREATE TABLE IF NOT EXISTS `zstack`.`VmMetadataDirtyVO` ( + `vmInstanceUuid` VARCHAR(32) NOT NULL, + `managementNodeUuid` VARCHAR(32) DEFAULT NULL, + `dirtyVersion` BIGINT NOT NULL DEFAULT 1, + `storageStructureChange` TINYINT(1) NOT NULL DEFAULT 0, + `retryCount` INT NOT NULL DEFAULT 0, + `nextRetryTime` TIMESTAMP NULL DEFAULT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`vmInstanceUuid`), + CONSTRAINT `fkVmMetadataDirtyVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) + REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE, + CONSTRAINT `fkVmMetadataDirtyVOManagementNodeVO` FOREIGN KEY (`managementNodeUuid`) + REFERENCES `ManagementNodeVO` (`uuid`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Poller CAS claim query optimization: WHERE managementNodeUuid IS NULL AND nextRetryTime <= NOW() +CREATE INDEX `idxVmMetadataDirtyUnclaimed` ON `VmMetadataDirtyVO` (`managementNodeUuid`, `nextRetryTime`); + +-- Clean up any old GC rows for vm metadata (from previous GC-based implementation) +DELETE FROM `GarbageCollectorVO` WHERE `name` LIKE 'update-vm-%-metadata-gc'; diff --git a/conf/globalConfig/vm.xml b/conf/globalConfig/vm.xml index 8563169b335..bae6e369e5f 100755 --- a/conf/globalConfig/vm.xml +++ b/conf/globalConfig/vm.xml @@ -317,4 +317,68 @@ java.lang.Boolean false + + + vm + deletion.gcInterval + update vm metadata interval + java.lang.Long + 30 + + + + vm + vm.metadata + save vm metadata + java.lang.Boolean + false + + + + vm + vm.metadata.ps.maxConcurrent + Max concurrent metadata writes per primary storage per MN. In dual-MN the actual global concurrency per PS = 2 × this value. + java.lang.Integer + 5 + + + + vm + vm.metadata.global.maxConcurrent + Max concurrent VM metadata updates globally per MN. In dual-MN the actual global concurrency = 2 × this value. + java.lang.Integer + 10 + + + + vm + vm.metadata.gc.initialDelaySec + Initial GC delay in seconds after API success. The first metadata update attempt happens after this delay. + java.lang.Integer + 10 + + + + vm + vm.metadata.maxRetry + Max retry count before giving up metadata flush. After this many failed attempts with exponential backoff, the dirty row is deleted and will auto-retry on the next API that modifies this VM. + java.lang.Integer + 5 + + + + vm + vm.metadata.dirty.pollIntervalSec + Dirty poller interval in seconds. The poller acts as a safety net to process dirty rows that were not handled by the immediate triggerFlush path (e.g., rows in backoff, orphaned rows after MN crash). + java.lang.Long + 5 + + + + vm + vm.metadata.dirty.batchSize + Max dirty rows to claim per poller cycle. Controls the batch size of CAS claim in each poller round. + java.lang.Integer + 20 + diff --git a/conf/persistence.xml b/conf/persistence.xml index aa295bcb365..dd97b857617 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -83,6 +83,7 @@ org.zstack.header.vm.VmInstanceEO org.zstack.header.vm.VmInstanceSequenceNumberVO org.zstack.header.vm.VmCrashHistoryVO + org.zstack.header.vm.VmMetadataDirtyVO org.zstack.appliancevm.ApplianceVmVO org.zstack.appliancevm.ApplianceVmFirewallRuleVO org.zstack.header.vm.VmDnsVO diff --git a/conf/serviceConfig/primaryStorage.xml b/conf/serviceConfig/primaryStorage.xml index 337ce4eaac3..06f4d94bc07 100755 --- a/conf/serviceConfig/primaryStorage.xml +++ b/conf/serviceConfig/primaryStorage.xml @@ -84,4 +84,7 @@ org.zstack.header.storage.primary.APIAddStorageProtocolMsg + + org.zstack.header.storage.primary.APIRegisterVmInstanceMsg + diff --git a/conf/springConfigXml/VmInstanceManager.xml b/conf/springConfigXml/VmInstanceManager.xml index 20e094378aa..f43256f7009 100755 --- a/conf/springConfigXml/VmInstanceManager.xml +++ b/conf/springConfigXml/VmInstanceManager.xml @@ -267,4 +267,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/design/2/vm-metadata-new-02h-compare.md b/docs/design/2/vm-metadata-new-02h-compare.md new file mode 100644 index 00000000000..2d8bc8e4530 --- /dev/null +++ b/docs/design/2/vm-metadata-new-02h-compare.md @@ -0,0 +1,103 @@ +> **导航** | [核心设计](../vm-metadata-01-design.md) | [持久化更新机制(新方案)](../vm-metadata-02-dirty-mark.md) | [注册与运维](../vm-metadata-03-registration.md) | [sblk 二进制协议](../vm-metadata-04-sblk.md) | [API 设计](../vm-metadata-05-api.md) +> +> **新方案子文档** | [概述](../vm-metadata-02-dirty-mark.md) | **对比与约束** + +# Dirty Mark + Poller vs GC 方案对比 + +本文从数据模型、标脏入口、消息链、并发控制、双 MN HA 五个维度,逐项对比新旧两个方案的差异。 + +| 属性 | 值 | +|------|-----| +| 文档版本 | 1.0 | +| 最后更新 | 2026-03-03 | + +--- + +## 1. 数据模型对比:VmMetadataDirtyVO vs GarbageCollectorVO + +| | GarbageCollectorVO | VmMetadataDirtyVO | +|---|---|---| +| 主键 | uuid(自动生成) | vmInstanceUuid(业务键) | +| 一个 VM 的行数 | 0~N(每次 submit 一行) | 0~1(主键去重) | +| context | JSON blob(@GC 字段序列化) | 无需(全量从 DB 构建) | +| status | Idle / Processing / Done | 无需(行存在=脏,删除=完成) | +| runnerClass | GC 子类全限定名 | 无需(固定逻辑) | +| 清理需求 | 24h 定期清理 Done 记录 | 无需(成功即删除) | + +--- + +## 2. 标脏入口对比:markDirty vs GC submit + +| | GC submit() | markDirty() | +|---|---|---| +| DB 操作 | INSERT 新行 | INSERT or UPDATE lastOpDate | +| 100 次 API | 100 行 GC | 1 行 Dirty | +| 去重 | 无法 deduplicateSubmit | 主键天然去重 | +| 跨 MN 路由 | 需 SubmitGCMsg 路由到 owner | 无需路由,本地 INSERT | +| 功能开关检查 | Interceptor 层检查 | markDirty 内统一检查 | +| 触发延迟 | GC timer 首次 10s | 立即唤醒(triggerFlushForVm)+ Poller 安全网 | + +--- + +## 3. 消息调用链对比 + +| | GC 方案 | 新方案 | +|---|---|---| +| 跳数 | 5 跳(Interceptor → SubmitGCMsg → GC.triggerNow → UpdateMsg → OnPSMsg → OnHypervisorMsg) | 4 跳(markDirty → Poller → UpdateMsg → OnPSMsg → OnHypervisorMsg) | +| 跨 MN 消息 | SubmitGCMsg 需 hash 环路由 | **无**(markDirty 本地 INSERT,Poller 本地执行) | +| 触发延迟 | GC timer(首次 10s) | 立即唤醒(markDirty → triggerFlush)| + +**去掉了 SubmitGCMsg 这一跳**——这是双 MN 复杂度的根源。不再需要路由、不再需要 reply 回退、不再需要 delegation。 + +--- + +## 4. 并发控制对比 + +| | GC 方案(四层) | 新方案(三层) | +|---|---|---| +| Layer 0 | Owner 归集(hash 环 isManagedByUs + delegation) | **移除**(DB CAS 认领天然解决) | +| Layer 1 | GC lockJob CAS (AtomicBoolean) | DB CAS 认领 | +| Layer 2 | per-VM ChainTask | per-VM ChainTask(不变) | +| Layer 3 | per-PS ChainTask | per-PS ChainTask(不变) | + +--- + +## 5. 双 MN HA:GC 方案六种极端情况的逐一对照 + +对应 GC 方案 §5.6 分析的六种极端情况: + +| # | GC 方案的极端情况 | 新方案情况 | +|---|---|---| +| 1 | delegation 消耗 retryCount | **不存在**。无 delegation,retryCount 仅在实际刷写失败时递增 | +| 2 | delegate 后 owner 宕机 | **不存在**。无 delegation。MN 宕机 → FK SET_NULL → 另一 MN 认领 | +| 3 | delegation 循环(hash 环反复变化) | **不存在**。无 delegation,无 hash 环依赖 | +| 4 | send-callback 间 MN 崩溃 | **改善**。markDirty 是本地 INSERT(已持久化),即使 Poller 在 send 后崩溃,FK SET_NULL 释放后另一 MN 接管。唯一丢失窗口缩小到"INSERT 执行到 MySQL commit 之间"(微秒级) | +| 5 | 大量 GC 同时 delegate | **不存在**。100 个 API → 1 行 dirty。Poller 认领 1 行 → ChainTask 执行 1 次。无 delegate 风暴 | +| 6 | makeDestination 返回自己 | **不存在**。无 hash 环依赖 | + +**结论**:GC 方案需要分析的 6 种极端情况,新方案中 **5 种压根不存在,1 种得到改善**。 + +--- + +## 6. 完整维度对比 + +| 维度 | GC 方案 | Dirty Mark 方案 | +|------|---------|-----------------| +| **DB 模型** | 一个任务一行 GC 记录 | 一个 VM 最多一行 dirty 标记 | +| **100 次 API 的 DB 行数** | 100 行(98 行立即 Done) | 1 行 | +| **去重能力** | deduplicateSubmit 不可用 | 主键天然去重 | +| **定时清理** | 需要 cleanUpCompletedJobs(24h) | 不需要(成功即删除) | +| **跨 MN 消息** | SubmitGCMsg hash 环路由 | 无(全部本地操作) | +| **双 MN 协调** | hash 环路由 + delegation + reply 回退 | DB CAS 认领(零协调) | +| **极端情况** | 6 种需分析 | 5 种不存在 + 1 种改善 | +| **MN 宕机接管延迟** | ~90s(或 nodeLeft 优化到 ~31s) | ~30s(nodeLeft 立即触发) | +| **MN 宕机处理代码** | 需 loadOrphanJobs + loadFromVO + setupTimer | nodeLeft → claimAndFlush()(一行) | +| **框架修改** | 需修改 GC 框架(loadOrphanJobs 状态过滤、索引) | 不需要修改任何框架 | +| **GC 行清理** | 24h 定期清理 Done 记录 | 不需要 | +| **并发控制层数** | 4 层 | 3 层 | +| **触发延迟** | GC timer 首次 10s | 立即唤醒 + Poller 安全网 | +| **retryCount 准确性** | delegation 失败也消耗 | 仅实际刷写失败消耗 | +| **代码复杂度** | 高(GC 子类 + Interceptor 路由 + delegation) | 低(1 VO + 1 PeriodicTask + markDirty) | +| **新增类** | UpdateVmInstanceMetadataGC + 修改 GC 框架 | VmMetadataDirtyVO + MetadataDirtyPoller | +| **触发统一性** | API 触发 vs 巡检触发逻辑不同 | 统一 markDirty() | +| **升级全量刷新** | LongJob + 逐个 submitGC | 批量 markDirty,Poller 自动处理 | diff --git a/docs/design/2/vm-metadata-new-02i-tech-debt.md b/docs/design/2/vm-metadata-new-02i-tech-debt.md new file mode 100644 index 00000000000..8d7a38fc7ad --- /dev/null +++ b/docs/design/2/vm-metadata-new-02i-tech-debt.md @@ -0,0 +1,284 @@ +> **导航** | [核心设计](../vm-metadata-01-design.md) | [持久化更新机制(新方案)](../vm-metadata-02-dirty-mark.md) | [GC 对比](vm-metadata-new-02h-compare.md) +> +> **技术债务分析** + +# VM 元数据 Dirty Mark + Poller 方案:技术债务深度分析 + +本文对 Dirty Mark + Poller 方案的代码实现进行逐层审计,识别当前存在或潜在的技术债务,并评估风险等级和修复建议。 + +--- + +## 评估标准 + +| 等级 | 含义 | +|------|------| +| **P0 — 正确性** | 可能导致数据不一致或功能错误 | +| **P1 — 设计缺陷** | 架构层面的遗漏,影响功能完整性或可扩展性 | +| **P2 — 运维隐患** | 不影响功能,但增加运维难度或隐含性能隐患 | +| **P3 — 代码卫生** | 代码风格、可维护性问题,无功能影响 | + +--- + +## P0 — 正确性问题 + +### 0.1 `buildVmInstanceMetadata()` 缺少 `@Transactional(readOnly=true)` + +**现状**:`VmInstanceBase.buildVmInstanceMetadata()` 执行 6+ 条 SELECT 查询(VmInstanceVO、VolumeVO×2、VmNicVO、VolumeSnapshotVO、VolumeSnapshotGroupVO、VolumeSnapshotGroupRefVO),每条 SELECT 是独立的自动提交事务。 + +**风险**:MySQL InnoDB 默认 REPEATABLE READ 隔离级别下,多条 SELECT 在不同事务快照上执行,可能读到不一致的中间状态: + +``` +T1: 查 VolumeVO → 返回 [vol-A, vol-B] +T2: 另一个 API 删除了 vol-B 的快照 +T3: 查 VolumeSnapshotVO WHERE volumeUuid IN (vol-A, vol-B) → 返回 vol-A 的快照(vol-B 的已删除) +``` + +最终元数据中 vol-B 存在但其快照不存在——数据自相矛盾。 + +**设计文档要求**:§1.3 明确写道 *"`buildVmInstanceMetadata()` 必须标注 `@Transactional(readOnly=true)`"*。 + +**修复**: +```java +@Transactional(readOnly = true) +private String buildVmInstanceMetadata(String vmInstanceUuid) { ... } +``` +> 注意:`private` 方法上的 Spring `@Transactional` 不生效(需 AOP 代理)。应将此方法提取到单独的 `@Component` Service 中,或改为 `public` 方法并通过自注入调用。这是 ZStack 中常见的事务管理 pattern。 + +**风险等级**:**P0 — 高**。时间窗口虽小(毫秒级),但在高并发 API 下问题可复现。 + +--- + +### 0.2 `claimDirtyRows()` Step2 查询不精确 — 可能重新提交已在处理的行 + +**现状**:`claimDirtyRows()` 分两步执行: +1. CAS UPDATE 认领一批 unclaimed 行 +2. SELECT WHERE `managementNodeUuid = myId` → 查询 "我认领的所有行" + +Step2 不仅返回本轮新认领的行,**还返回之前认领但仍在 ChainTask pipeline 中处理的行**。 + +**影响链**: +``` +T0: Poller 认领 vm-A → submitFlushTask → 进入 ChainTask 队列 (running) +T5: Poller 再次运行 → claimDirtyRows(): + Step1: 认领 vm-B (新行) + Step2: SELECT WHERE myId → 返回 [vm-A, vm-B] + → submitFlushTask(vm-A) 和 submitFlushTask(vm-B) + → vm-A 的 per-VM ChainTask 已有 running+pending → exceedMaxPendingCallback + → releaseClaim(vm-A) → 设置 managementNodeUuid=NULL +``` + +**问题**:`releaseClaim` 将正在被当前 MN 处理的 vm-A 的认领释放,使其变为 unclaimed。此时另一个 MN 可以认领 vm-A 并启动并发刷写。虽然 per-VM ChainTask 在每个 MN 内部保证串行,但跨 MN 的并发刷写不受 Layer 2 保护。 + +**实际影响**:低。两个 MN 同时刷写同一 VM 的元数据,最终结果仍是最新全量 payload(幂等写入)。但会浪费资源且可能导致 Agent 端竞争。 + +**修复方案 A — 精确查询**:CAS UPDATE 后用 LAST_INSERT_ID 技巧或用 RETURNING 子句(MySQL 8.0+)获取被更新的行。ZStack 用 MySQL 5.7,可改为: +```java +// 先标记本轮认领的行(使用临时标记字段或 retryCount 上的 trick) +// 或用更精确的 SELECT: +return Q.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.managementNodeUuid, myId) + .isNull(VmMetadataDirtyVO_.nextRetryTime) // 新认领的行 nextRetryTime 为 null + .list(); +``` +> 注意:这个 workaround 不完美(首次 markDirty 的行 nextRetryTime 也是 null)。 + +**修复方案 B — 认领时记录认领批次**:在 CAS UPDATE 中设置一个 `claimBatch` 标记(如当前时间戳),后续 SELECT 按此标记过滤。 + +**修复方案 C — 不修复,接受现状**:ChainTask 的 deduplication 已兜底。exceedMaxPendingCallback 中的 releaseClaim 改为 **不释放**(因为有可能正在处理)或改为"仅在 managementNodeUuid == myId 时释放"。 + +**风险等级**:**P0 — 中**。存在跨 MN 并发刷写窗口,但幂等写入保证最终一致。 + +--- + +### 0.3 `updateOnFailure` 注解属性未实现 + +**现状**:`@MetadataImpact` 注解声明了 `boolean updateOnFailure() default false;`,设计上用于标记"API 失败时也需要更新元数据"的场景。 + +当前 `VmMetadataUpdateInterceptor` 的实现: +```java +if (apiEvent.getError() != null) { + return; // 直接跳过,不检查 updateOnFailure +} +``` + +**影响**:如果某个 API(如部分成功的批量操作)在失败时也会修改 DB 状态,则元数据不会被更新,导致存储上的元数据与 DB 不一致。 + +**修复**: +```java +MetadataImpact impact = pendingApiImpacts.get(apiEvent.getApiId()); +if (apiEvent.getError() != null && !impact.updateOnFailure()) { + return; +} +``` +> 需要在 pendingApis 中额外缓存 annotation 信息。 + +**风险等级**:**P0 — 中**。取决于是否有 API 实际设置了 `updateOnFailure=true`。当前所有 API 使用默认值 `false`,故暂无实际影响。 + +--- + +## P1 — 设计缺陷 + +### 1.1 `VmUuidResolver` 机制未使用 — 多 VM API 只提取单个 UUID + +**现状**:`MetadataImpact` 注解定义了 `resolver()` 属性和多种 `VmUuidResolver` 实现(DefaultVmUuidResolver、VolumeBasedVmUuidResolver、ResourceBasedVmUuidResolver),支持从 API 消息中解析出**多个** vmUuid。 + +但 `VmMetadataUpdateInterceptor.extractVmInstanceUuid()` 使用反射调用 `getVmInstanceUuid()` / `getResourceUuid()`,只返回**单个** vmUuid。 + +**影响**:批量 API(如 `APICreateVolumesSnapshotMsg` 涉及多个 Volume、多个 VM)只能提取到一个 vmUuid,其他受影响的 VM 不会被 markDirty。 + +**修复**:Interceptor 应使用 `VmUuidResolverRegistry` 获取对应的 resolver,调用 `resolve()` 返回 `List`,对每个 vmUuid 调用 `markDirty()`。 + +**风险等级**:**P1 — 高**。影响功能正确性,但仅限于批量 API 场景。 + +--- + +### 1.2 Payload 大小保护未实现 + +**现状**:设计文档 §10 明确要求: +- \> 8MB → WARN 日志 +- \> 30MB → ERROR + 拒绝写入 + 释放认领 + +当前代码中 `doFlush()` 和 `VmInstanceBase.doHandleUpdateVmInstanceMetadata()` 均未做大小检查。 + +**影响**:VM 有成千上万快照时,元数据 payload 可能达到数十 MB,导致: +- sblk LV 空间不足(元数据 LV 默认 32MB) +- 网络传输超时 +- Agent 内存压力 + +**修复**:在 `VmInstanceBase.doHandleUpdateVmInstanceMetadata()` 中,`buildVmInstanceMetadata()` 之后添加大小检查。 + +**风险等级**:**P1 — 中**。正常使用不会触发,但缺乏保护机制。 + +--- + +### 1.3 `storageStructureChange` OR 升级的粘性问题 + +**现状**:`markDirty()` 的 INSERT ON DUPLICATE KEY UPDATE 对 `storageStructureChange` 使用 `OR` 升级: +```sql +storageStructureChange = storageStructureChange OR VALUES(storageStructureChange) +``` + +一旦标记为 STORAGE(true),在整个 dirty 行生命周期内不会降级为 CONFIG(false),**即使 STORAGE 变更已被成功刷写**。 + +**场景**: +``` +T0: STORAGE API → markDirty(vm, true) → storageStructureChange=true +T1: 认领, snapshotVersion=1, flush with OP type 2 → 成功 +T2: CONFIG API → markDirty(vm, false) → storageStructureChange = true OR false = true +T3: version mismatch → release → 重新认领 +T4: flush with OP type 2 — 但此时只有 CONFIG 变更! +``` + +T4 使用了不必要的 OP type 2,Agent 做了多余的存储拓扑处理。 + +**影响**:性能浪费,不影响正确性。OP type 2 是 OP type 1 的超集,多做不少做。 + +**修复**:在 `onFlushSuccess` 的 version mismatch 分支中,额外重置 `storageStructureChange=false`(已成功刷写的 STORAGE 变更不需要在下轮重复处理)。但这引入了新的竞态——重置和 markDirty 的 OR 更新可能冲突。需要用 CAS 方式处理。 + +**风险等级**:**P1 — 低**。仅性能影响,正确性不受影响。 + +--- + +## P2 — 运维隐患 + +### 2.1 无监控指标 + +**现状**:VmMetadataDirtyMarker 没有暴露任何运维指标: +- dirty 行总数、认领中行数 +- 刷写成功/失败次数、延迟 +- 退避中行数、达最大重试次数的事件计数 +- Poller 执行耗时 + +**影响**:生产环境中难以判断元数据更新是否健康运行,排查问题依赖日志。 + +**修复**:引入 ZStack 内部的 Metrics 机制(如果有),或至少提供一个查询 API 返回当前 dirty table 统计。 + +--- + +### 2.2 Poller 在功能关闭时仍空转 + +**现状**:`vm.metadata.enabled=false` 时,`markDirty()` 会提前返回,不会产生 dirty 行。但 Poller 仍然每 5 秒执行 `claimAndFlush()` → 执行 SELECT → 返回 0 行。 + +**影响**:微小的 DB 开销(一次空 SELECT < 1ms),但在大量 MN 集群中可能有细微影响。 + +**修复**:在 `claimAndFlush()` 入口检查功能开关,或在 `managementNodeReady()` 中根据开关决定是否启动 Poller,并监听开关变更。 + +--- + +### 2.3 `nodeLeft` 在回调线程上执行 DB 操作 + +**现状**:`nodeLeft()` 直接调用 `claimAndFlush()`,其中包含 CAS UPDATE + SELECT + 多次 ChainTask 提交。 + +**影响**:如果 `nodeLeft` 是在管理节点心跳检测线程上被调用的,DB 操作可能阻塞心跳处理。 + +**修复**: +```java +@Override +public void nodeLeft(ManagementNodeInventory inv) { + thdf.submitTask(() -> claimAndFlush()); +} +``` + +--- + +### 2.4 指数退避常量硬编码 + +**现状**:`BASE_DELAY_SECONDS=10` 和 `MAX_EXPONENT=10` 为编译时常量,不可通过 GlobalConfig 动态调整。 + +**影响**:如果退避策略不适合特定部署环境(如 PS 恢复缓慢需要更长退避),需要重新编译部署。 + +**修复**:将退避参数作为 GlobalConfig 暴露,或至少提供系统属性覆盖。 + +--- + +## P3 — 代码卫生 + +### 3.1 `VmMetadataUpdateInterceptor.submitMarkDirty()` 访问限定符 + +**现状**:`submitMarkDirty()` 从 `void` (package-private, 供 MetadataCascadeExtension 等调用) 改为 `private`。但 `MetadataCascadeExtension` 现在直接注入 `VmMetadataDirtyMarker` 调用 `markDirty()`,不再需要通过 Interceptor 中转。 + +**影响**:无。重构已完成,旧的 package-private 契约不再需要。 + +--- + +### 3.2 设计文档中的遗留 GC 注释 + +**现状**:部分代码注释仍引用 GC 概念: +- `VmMetadataCanonicalEvents.java` 可能存在过时的 GC 相关事件定义 +- 部分 Javadoc 中可能残留 "GC" 字样 + +**影响**:无功能影响,但降低代码可读性。 + +--- + +### 3.3 `MetadataImpact.java` 中内部类的编译依赖 + +**现状**:`MetadataImpact` 是一个注解类,但其内部定义了多个 `VmUuidResolver` 实现类(`VolumeBasedVmUuidResolver`、`ResourceBasedVmUuidResolver`),这些类引用了 `SQL`、`VolumeVO`、`VolumeMessage` 等外部依赖。将 "实现类" 放在 "注解类" 内部是不寻常的做法。 + +**影响**:注解类的编译依赖过重,且内部类的 `@Autowired` 字段注入不会自动生效(内部类不是 Spring Bean)。 + +**修复**:将 Resolver 实现类移出注解类,单独作为 Spring Component 注册。 + +--- + +## 总结矩阵 + +| # | 问题 | 等级 | 状态 | 修复说明 | +|---|------|------|------|----------| +| 0.1 | buildVmInstanceMetadata 缺 @Transactional | P0 | ✅ 已修复 | 提取至 `VmMetadataBuilder` Spring Component,标注 `@Transactional(readOnly=true)` | +| 0.2 | claimDirtyRows Step2 查询不精确 | P0 | ✅ 已修复 | `exceedMaxPendingCallback()` 不再调用 `releaseClaim()`,避免释放正在处理的行 | +| 0.3 | updateOnFailure 未实现 | P0 | ✅ 已修复 | `MetadataImpactInfo` 存储 `updateOnFailure` 标志,`beforePublishEvent` 据此决定是否跳过 | +| 1.1 | VmUuidResolver 未使用 | P1 | ✅ 已修复 | 删除 compute `MetadataImpact`(含破损内部类),创建 4 个 `VmUuidFromApiResolver` 实现并注册为 Spring Bean,Interceptor 使用 Resolver 链解析 `List` vmUuids | +| 1.2 | Payload 大小保护未实现 | P1 | ✅ 已修复 | `VmInstanceBase.doHandleUpdateVmInstanceMetadata()` 中 build 后检查 payload 大小,>8MB WARN / >30MB 拒绝 | +| 1.3 | storageStructureChange OR 粘性 | P1 | ✅ 已修复 | `onFlushSuccess` version mismatch 分支重置 `storageStructureChange=false` | +| 2.1 | 无监控指标 | P2 | ⏸ 延后 | 计划下一迭代引入 Metrics | +| 2.2 | Poller 功能关闭时空转 | P2 | ✅ 已修复 | `claimAndFlush()` 入口检查 `VmGlobalConfig.VM_METADATA` 开关 | +| 2.3 | nodeLeft 在回调线程执行 DB | P2 | ✅ 已修复 | `nodeLeft()` 通过 `thdf.submit(Task)` 异步执行 `claimAndFlush()` | +| 2.4 | 退避常量硬编码 | P2 | ⏸ 延后 | 视需求通过 GlobalConfig 暴露 | +| 3.1 | submitMarkDirty 访问限定符 | P3 | ✅ 已修复 | 重构后为 package-private,仅供同包使用 | +| 3.2 | 遗留 GC 注释 | P3 | ✅ 已修复 | 代码文件中已无遗留 GC 注释 | +| 3.3 | MetadataImpact 内部类设计 | P3 | ✅ 已修复 | 删除 compute `MetadataImpact.java`/`MetadataImpactLevel.java`,Resolver 作为独立 Spring Bean | + +> **本轮修复 11/13 项**。剩余 2 项(2.1 监控指标、2.4 退避常量)为低优先级,计划后续迭代处理。 +> +> **附加修复**:`VmInstanceMetadataDTO.ResourceMetadata` 的 `systemTags`/`resourceConfigs` 字段类型从 `List` 修正为 `String`(Base64 编码字符串)。 diff --git a/docs/design/vm-metadata-01-design.md b/docs/design/vm-metadata-01-design.md new file mode 100644 index 00000000000..8d9bb0d8fb0 --- /dev/null +++ b/docs/design/vm-metadata-01-design.md @@ -0,0 +1,694 @@ +# 核心设计 + +## 1. 概述 + +虚拟机元数据功能用于将 VM 及其关联资源(云盘、网卡、快照、SystemTag、ResourceConfig)的关键信息持久化到主存储上,以支持跨平台/灾难恢复场景下的虚拟机注册恢复。 + +### 1.1 全局配置 + +增加全局配置项 `vm.metadata.enabled`(Boolean,**默认为 false**),开启/关闭记录虚拟机元数据。 + +**理由**:注册虚拟机仅用于有容灾需求的场景。对于 99.9% 普通用户来说不会用到此功能。 + +--- + +## 2. 核心 DTO 结构 + +### 2.1 VmInstanceMetadataDTO + +``` +VmInstanceMetadataDTO +├── schemaVersion: String // 元数据 schema 版本(与 zsv 数据库版本一致) +├── vm: ResourceMetadata // 虚拟机自身 +├── volumes: List // 根盘 + 数据盘 +├── nics: List // 网卡(仅记录,注册时不恢复) +├── snapshots: Map> // volumeUuid → List +├── snapshotGroups: List // List +├── snapshotGroupRefs: List // List +├── snapshotReferences: Map> // volumeUuid → List +├── snapshotReferenceTrees: Map> // volumeUuid → List +└── isTemplated: boolean // 是否为模板虚拟机(默认 false) +``` + +### 2.2 ResourceMetadata + +``` +ResourceMetadata +├── resourceUuid: String // 资源 UUID(冗余,必须与 vo 内部 uuid 一致) +├── vo: String // VO 全量 JSON 明文 +├── systemTags: String // 该资源所有 SystemTagVO JSON 列表的整体 Base64 编码 +└── resourceConfigs: String // 该资源所有 ResourceConfigVO JSON 列表的整体 Base64 编码 +``` + +### 2.3 快照引用数据结构 + +`snapshotReferences` 和 `snapshotReferenceTrees` 使用 `Map>` 而非 `Map`,原因是同一 volumeUuid 可能对应多条引用记录。 + +| VO | 含义 | 注册意义 | +|----|------|----------| +| `VolumeSnapshotReferenceVO` | 记录快照的引用关系(如模板创建、linked clone 时快照被引用) | 不恢复会导致快照引用计数为 0,可能被 GC 误删 | +| `VolumeSnapshotReferenceTreeVO` | 记录引用树结构,维护引用链路 | 维护引用层级关系,防止链路断裂 | + +--- + +## 3. 编码策略 — per-Resource 字段级 Base64 + +**设计决策**:DTO 整体为明文 JSON 写入存储介质,**不对整体 JSON 做 Base64 编码**。对每个 `ResourceMetadata` 中的 `systemTags` 和 `resourceConfigs` 字段采用 **per-Resource 整体 Base64 编码**:将该资源的所有 SystemTagVO JSON 组成列表后做一次 Base64;ResourceConfig 同理。 + +- **sblk**:Slot Payload = DTO JSON 明文(其中每个资源的 systemTags 是一个 Base64 字符串,resourceConfigs 同理) +- **local/NFS**:文件内容 = DTO JSON 明文(编码方式同上) + +**理由**: + +1. 避免对整体 JSON 做 Base64 带来的 **4/3 空间膨胀**(10MB 元数据膨胀为 ~13.3MB) +2. SystemTag 和 ResourceConfig 的 JSON 内容可能包含特殊字符(多层嵌套、用户自定义内容),对其做 Base64 可避免 JSON 转义问题 +3. VO、快照等主体数据保持明文,可通过 `APIReadVmInstanceMetadataFromPrimaryStorageMsg`(§12.2)读取完整元数据 JSON,屏蔽底层存储差异(sblk 二进制协议 vs local/NFS 文件) +4. 一致性检查 API 可直接对主体数据做结构化比较 +5. per-Resource 整体编码(而非逐元素编码)减少 Base64 header 开销和解码次数 + +**编解码流程**: + +``` +写入: + 对每个 ResourceMetadata: + systemTags = Base64( JSON.toJsonString( List ) ) + resourceConfigs = Base64( JSON.toJsonString( List ) ) + DTO → JSON → 写入存储 + +读取: + 存储 → JSON → DTO + 对每个 ResourceMetadata: + List = JSON.parseArray( Base64Decode(systemTags) ) + List = JSON.parseArray( Base64Decode(resourceConfigs) ) +``` + +**Checksum 不放在 DTO 内部**: + +- sblk:Slot 结构自带 Checksum 字段 +- local/NFS:tmp + fsync + rename 原子写入 + `_checksum` 字段保证完整性 + +--- + +## 4. 序列化关注点 + +| 关注点 | 方案 | +|--------|------| +| 嵌套 JSON 转义 | 保持 String 类型,Gson 自动处理双重转义/反转义 | +| JSON 字段顺序一致性 | 使用统一的 `JSONObjectUtil`(Gson),字段顺序由声明顺序决定 | +| null 字段处理 | Gson 默认跳过 null 字段,反序列化时 Java 默认值与 null 语义一致 | +| SystemTag/ResourceConfig 过滤 | 序列化前过滤,只保留影响虚拟机 xml 的 tag/config(见 §4.1) | +| 元数据大小 | 极端场景(24盘×256快照)约 5-10MB,仅 SystemTag/ResourceConfig 字段 Base64 编码,整体膨胀可忽略(主体数据无编码开销),在 sblk 单 Slot 32MB 内 | +| 不压缩的理由 | 正常场景 <100KB,极端场景罕见;压缩会增加 Agent 依赖和调试复杂度;未来可通过 bump HeaderVersion 引入 CompressionType 字段支持 | + +### 4.1 SystemTag/ResourceConfig 过滤规则 + +过滤采用**白名单注册机制**: + +1. 新增 `MetadataRelevantTagRegistry` 接口,各插件通过 `@Component` 注册影响 VM XML 的 tag category/key +2. 编译期 `CheckerCase` 校验新增 SystemTag 定义是否在 Registry 中声明 +3. 初始白名单:`bootOrder`、`sshKey`、`hostname`、`bootMode`、`qga`、`cdrom`、`nicDriver`、`cpuModel`、`machineType`、`usbDevice` 等 +4. ResourceConfig 同理,只保留 `vm.` 和 `volume.` 前缀的配置项 + +**可维护性保证**:新增 SystemTag/ResourceConfig/API 时,若未在白名单中声明,CI 报错,强制开发者判断是否纳入元数据。 + +**统一 CI 检查机制 — `MetadataWhitelistChecker`**: + +将 API 标注检查、SystemTag 白名单检查、ResourceConfig 前缀检查合并为单一 CheckerCase: + +```java +public class MetadataWhitelistChecker extends PostBuildCheckerCase { + @Override + public void check() { + // Part 1: API @MetadataImpact 检查 + Set> allApiMsgs = BeanUtils.reflections + .getSubTypesOf(APIMessage.class); + for (Class msgClass : allApiMsgs) { + if (isQueryOrGetApi(msgClass)) continue; + assertMetadataImpactPresent(msgClass); + } + + // Part 2: SystemTag 白名单检查 + // 扫描所有 SystemTag.define() 调用,要求每个 tag 被某个 + // MetadataWhitelistProvider 声明为 relevant 或 irrelevant + Set allDefinedTags = scanAllSystemTagDefinitions(); + Set registeredTags = collectFromProviders(); + for (String tag : allDefinedTags) { + if (!registeredTags.contains(tag)) { + fail("SystemTag '" + tag + "' not declared in MetadataWhitelistProvider"); + } + } + + // Part 3: ResourceConfig 前缀检查 + // 所有 ResourceConfig category 必须在 Provider 中声明是否纳入 + Set allConfigCategories = scanAllResourceConfigCategories(); + Set registeredCategories = collectCategoriesFromProviders(); + for (String cat : allConfigCategories) { + if (!registeredCategories.contains(cat)) { + fail("ResourceConfig category '" + cat + "' not declared"); + } + } + } +} +``` + +**MetadataWhitelistProvider 接口**(统一 SystemTag + ResourceConfig + API 白名单注册): + +```java +public interface MetadataWhitelistProvider { + /** 此模块需要纳入 VM 元数据的 SystemTag 模式列表 */ + List getRelevantTagPatterns(); + + /** 此模块需要纳入 VM 元数据的 ResourceConfig category 前缀列表 */ + List getRelevantResourceConfigCategories(); +} +``` + +各插件通过 `@Component` 实现 `MetadataWhitelistProvider` 注册自己的 SystemTag 和 ResourceConfig。 + +--- + +## 5. 反序列化关注点 + +| 关注点 | 方案 | +|--------|------| +| 二步反序列化 | 先反序列化 DTO,再反序列化各 ResourceMetadata.vo 为具体 VO 类 | +| resourceUuid 一致性校验 | 反序列化后校验 `resourceMetadata.resourceUuid == parsedVO.getUuid()` | +| VO 字段版本兼容(多字段) | Gson 忽略缺失字段,填充 Java 默认值 | +| VO 字段版本兼容(少字段) | Gson 忽略未知字段 | +| Base64 解码失败 | systemTags/resourceConfigs 元素 Base64 解码失败时直接报错拒绝注册,不做部分恢复 | +| VO JSON 字段范围 | 包含所有非 `@Transient` 持久化字段;注册时 `id`(自增主键)由 DB 重新生成,`createDate` 保留原值,`lastOpDate` 替换为注册时间 | + +--- + +## 6. schemaVersion 版本规则 + +### 6.1 版本号定义 + +`schemaVersion` 使用 ZStack 数据库版本号,即 **`dbf.getDbVersion()`** 的返回值,与数据库 schema 版本完全一致。 + +**理由**:schemaVersion 直接反映数据库 schema 版本,VO 字段的增删改由数据库升级脚本驱动,与数据库版本绑定最为准确。 + +**比较规则**:注册时要求 `metadata.schemaVersion == dbf.getDbVersion()`,不匹配则拒绝。 + +### 6.2 版本生命周期 + +- 序列化时由 `VmMetadataBuilder` 自动填充 `dbf.getDbVersion()` +- 注册时若 schemaVersion 与当前数据库版本不匹配,拒绝注册 +- 升级后通过全量刷新(批量 `markDirty`,Poller 自动处理)将所有 VM 的元数据更新到新版本 +- 刷写端不依赖旧版本元数据,从 DB 直接构建新版本元数据覆盖写入 + +### 6.3 版本兼容规则 + +**核心规则**:元数据版本不兼容时,默认拒绝注册。仅当注册 API 显式指定 `force=true` 时允许跨版本注册(缺失字段置为 null,warnings 中记录)。 + +- `metadata.schemaVersion == dbf.getDbVersion()` → 匹配,正常注册 +- `metadata.schemaVersion != dbf.getDbVersion()` + `force=false` → 拒绝注册(`METADATA_VERSION_MISMATCH`) +- `metadata.schemaVersion != dbf.getDbVersion()` + `force=true` → 允许注册,Gson 反序列化时缺失字段自动填充 Java 默认值,warnings 中记录版本差异 + +### 6.4 升级时间窗口 + +- 升级后批量 `markDirty` 所有已启用元数据的 VM,Poller 自动分批处理,ChainTask 自动限流(详见 [Part 2 §9](vm-metadata-02-dirty-mark.md)) +- 10 万 VM × 平均 50ms/VM ≈ 5000 秒 ≈ 83 分钟 +- 窗口期内若需注册 VM,可使用 `APIUpdateVmMetadataMsg` 单独更新指定 VM 的元数据 + +--- + +## 7. @MetadataImpact 注解与 API 拦截 + +### 7.1 注解定义 + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MetadataImpact { + MetadataImpactLevel value() default MetadataImpactLevel.CONFIG; + Class resolver() default DirectVmUuidResolver.class; + boolean updateOnFailure() default false; +} + +public enum MetadataImpactLevel { + NONE, // 无关虚拟机配置 + CONFIG, // 虚拟机普通配置更新 + STORAGE // 虚拟机存储结构更新(快照/存储迁移等) +} +``` + +### 7.2 拦截范围澄清(重要) + +**拦截器不是对所有 API 生效**。正确逻辑: + +1. `ApiMetadataBehaviorsCheckerCase` 扫描所有 `APIMessage` 子类 +2. **只有显式标注了 `@MetadataImpact` 且 level ≠ `NONE` 的 API** 才触发元数据更新 +3. **未标注 `@MetadataImpact` 的 API 不触发任何操作** +4. `CheckerCase` 在 CI 编译阶段会报错,要求开发者对每个新增 API **显式声明 `@MetadataImpact`** +5. opt-out 的含义是:**必须标注,不标注 CI 报错**。`CONFIG` 是默认标注值(减少显式声明的工作量),不是"未标注时默认触发" + +### 7.3 STORAGE 标记的特殊处理 + +`@MetadataImpact(STORAGE)` 类 API 在设计文档中标识该操作涉及存储拓扑变更。 + +> **op_type 由控制面指定**:`@MetadataImpact(CONFIG)` → OP type=1(`CONFIG_UPDATE`),`@MetadataImpact(STORAGE)` → OP type=2(`STORAGE_CHANGE`)。OP type 通过 `storageStructureChange` 字段贯穿整条消息链(`VmMetadataDirtyVO` → `UpdateVmInstanceMetadataMsg` → `UpdateVmInstanceMetadataOnPrimaryStorageMsg` → `UpdateVmInstanceMetadataOnHypervisorMsg`)。 +> +> `@MetadataImpact(STORAGE)` 注解的作用: +> - **直接影响 Agent 写入行为**:sblk 场景下决定 Header `PendingOp` 字段的值(1 或 2),影响崩溃恢复策略 +> - CI 编译期校验:标识哪些 API 涉及存储变更 +> - 文档和代码可读性:开发者一目了然地知道该 API 涉及存储拓扑 + +### 7.4 updateOnFailure 触发条件 + +| 条件 | 是否触发 | +|------|----------| +| `updateOnFailure=true` + API 成功 | 触发 | +| `updateOnFailure=true` + API 失败(实际执行了部分逻辑) | 触发 | +| `updateOnFailure=true` + API 参数校验失败(未进入业务逻辑) | **不触发** | +| `updateOnFailure=false` + API 失败 | 不触发 | + +**实现**:只有当 ThreadContext 中存在 `__metadata_vmUuids__`(即至少走过了 `BeforeDeliveryMessageInterceptor` 预捕获阶段)时才触发更新。纯参数校验失败不经过拦截器,自然不触发。 + +### 7.5 影响虚拟机元数据的 API 清单 + +#### SystemTag 相关 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APICreateSystemTagMsg` | CONFIG | `ResourceUuidToVmResolver` | false | +| `APIDeleteTagMsg` | CONFIG | `ResourceUuidToVmResolver` | false | +| `APIUpdateSystemTagMsg` | CONFIG | `ResourceUuidToVmResolver` | false | +| `APISetVmBootOrderMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APIDeleteVmBootModeMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APIDeleteVmSshKeyMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APIDeleteVmHostnameMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APISetVmQgaMsg` | CONFIG | `DirectVmUuidResolver` | false | + +#### ResourceConfig 相关 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIUpdateResourceConfigMsg` | CONFIG | `ResourceUuidToVmResolver` | false | +| `APIDeleteResourceConfigMsg` | CONFIG | `ResourceUuidToVmResolver` | false | + +#### 磁盘加载卸载 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIAttachDataVolumeToVmMsg` | STORAGE | `VolumeToVmResolver` | false | +| `APIDetachDataVolumeFromVmMsg` | STORAGE | `PreCaptureVolumeToVmResolver` | false || `APIDeleteDataVolumeMsg` | STORAGE | `PreCaptureVolumeToVmResolver` | false | +| `APIRecoverDataVolumeMsg` | STORAGE | `VolumeToVmResolver` | false || `APIReimageVmInstanceMsg` | STORAGE | `DirectVmUuidResolver` | false | + +#### 存储迁移 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIPrimaryStorageMigrateVmMsg` | STORAGE | `DirectVmUuidResolver` | false | +| `APIPrimaryStorageMigrateVolumeMsg` | STORAGE | `VolumeToVmResolver` | false | + +#### 快照相关 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APICreateVolumesSnapshotMsg` | STORAGE | `VolumeToVmResolver` | false | +| `APICreateVolumeSnapshotGroupMsg` | STORAGE | `DirectVmUuidResolver` | false | +| `APIDeleteVolumeSnapshotMsg` | STORAGE | `SnapshotToVmResolver` | false | +| `APIDeleteVolumeSnapshotGroupMsg` | STORAGE | `SnapshotGroupToVmResolver` | false | +| `APIRevertVolumeFromSnapshotMsg` | STORAGE | `SnapshotToVmResolver` | false | +| `APIFlattenVolumeMsg` | STORAGE | `VolumeToVmResolver` | false | + +#### 克隆/模板 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APICloneVmInstanceMsg` | STORAGE | `DirectVmUuidResolver` | **true** | +| `APICreateTemplatedVmInstanceFromVmInstanceMsg` | STORAGE | `DirectVmUuidResolver` | **true** | +| `APICreateVmInstanceFromTemplatedVmInstanceMsg` | STORAGE | `DirectVmUuidResolver` | false | +| `APIExportImageFromBackupStorageMsg` | STORAGE | `DirectVmUuidResolver` | false | +> **说明**:`APICloneVmInstanceMsg` 和 `APICreateTemplatedVmInstanceFromVmInstanceMsg` 的 Resolver 解析的是**源 VM** 的 UUID。新创建的 VM 的元数据由新建 VM 流程末尾自动生成(与正常创建 VM 一致),拦截器无需关心。 +#### 模板虚拟机身份转换 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|------------------| +| `APIConvertVmInstanceToTemplatedVmInstanceMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APIConvertTemplatedVmInstanceToVmInstanceMsg` | CONFIG | `DirectVmUuidResolver` | false | + +> **说明**:模板身份转换仅涉及 `TemplatedVmInstanceVO` 标记行的增删和 `isTemplated` 字段的变更,不改变存储拓扑,使用 CONFIG 级别。 + +#### 卷大小变更 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIResizeRootVolumeMsg` | STORAGE | `DirectVmUuidResolver` | false | +| `APIResizeDataVolumeMsg` | STORAGE | `VolumeToVmResolver` | false | + +#### VM 配置变更 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|------------------| +| `APIUpdateVmInstanceMsg` | CONFIG | `DirectVmUuidResolver` | false | + +#### 网卡相关 + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIChangeVmNicNetworkMsg` | CONFIG | `NicToVmResolver` | false | +| `APIAttachVmNicToVmMsg` | CONFIG | `DirectVmUuidResolver` | false | +| `APIChangeVmNicStateMsg` | CONFIG | `NicToVmResolver` | false | +| `APIDeleteVmNicMsg` | CONFIG | `PreCaptureNicToVmResolver` | false | +| `APIDetachNicFromBondingMsg` | CONFIG | `NicToVmResolver` | false | +| `APIAttachNicToBondingMsg` | CONFIG | `NicToVmResolver` | false | + +#### CD-ROM + +| API | Level | Resolver | updateOnFailure | +|-----|-------|----------|-----------------| +| `APIDeleteVmCdRomMsg` | CONFIG | `DirectVmUuidResolver` | false | + +--- + +## 8. VmUuid 解析器(Resolver) + +### 8.1 解析时机(重要) + +Resolver 在**两个时机**捕获 vmUuid: + +1. **API 执行前**(`BeforeDeliveryMessageInterceptor`):预解析 vmUuid 并缓存到 `pendingApis` ConcurrentHashMap 中(key = apiId) +2. **API 成功后**(`beforePublishEvent`):从 `pendingApis` 读取缓存的 vmUuid,调用 `markDirty(vmUuid)` 标脏 + +**执行线程说明**:`beforeDeliveryMessage()` 在消息投递线程中同步执行(`CloudBusImpl3.doSendAndCallbackFromQueue` 内部调用)。Resolver 的 DB 查询在此线程中执行,对单次 API 延迟影响极小(单次简单查询 <1ms)。 + +### 8.2 Resolver 接口 + +```java +public interface VmUuidFromApiResolver { + /** + * 从 API 消息解析出需要更新元数据的 vmUuid 列表 + * @return vmUuid 列表(可能为多个) + */ + List resolve(APIMessage msg); +} +``` + +### 8.3 内置 Resolver 实现 + +| Resolver | 逻辑 | +|----------|------| +| `DirectVmUuidResolver` | 从 `msg.getVmInstanceUuid()` 直接获取 | +| `VolumeToVmResolver` | 通过 volumeUuid 查 `VolumeVO.vmInstanceUuid` | +| `PreCaptureVolumeToVmResolver` | 同 VolumeToVmResolver,但标记为需要预捕获(API 执行前获取) | +| `SnapshotToVmResolver` | snapshotUuid → VolumeSnapshotVO.volumeUuid → VolumeVO.vmInstanceUuid | +| `SnapshotGroupToVmResolver` | groupUuid → refs → 多个 volumeUuid → 多个 vmUuid | +| `ResourceUuidToVmResolver` | resourceUuid 可能是 VM/Volume/NIC,逐一判断 | +| `NicToVmResolver` | nicUuid → VmNicVO.vmInstanceUuid | +| `PreCaptureNicToVmResolver` | 同 NicToVmResolver,标记为需要预捕获 | + +### 8.4 新增 API 缺少注解的检测 + +`ApiMetadataBehaviorsCheckerCase`(编译期/CI 测试): + +1. 扫描所有 `APIMessage` 子类 +2. 检查是否标注了 `@MetadataImpact` +3. **未标注 → CI 报错**,错误信息提示:`"API {className} 缺少 @MetadataImpact 注解。请判断该 API 是否与虚拟机配置相关,并添加 @MetadataImpact(NONE/CONFIG/STORAGE) 注解。"` +4. 标注了但 resolver 类不存在 → CI 报错 + +--- + +## 9. 存储层元数据 + +### 9.1 元数据存储路径 + +| 存储类型 | 路径 | +|----------|------| +| sblk | `/dev/{vg_uuid}/{vm_uuid}_vmmeta` | +| local/NFS | `{存储挂载路径}/rootVolumes/acct-{id}/vol-{uuid}/{vm_uuid}_vmmeta` | +| ceph/zbs/vhost | 当前版本不支持,后续按需扩展 | + +### 9.2 元数据跟随根盘迁移 + +存储迁移 API(`APIPrimaryStorageMigrateVmMsg`)已标记为 `@MetadataImpact(STORAGE)`。迁移流程增加步骤: + +1. 迁移根盘数据 +2. 在新存储创建元数据文件 +3. 删除旧存储的元数据文件(异步清理,清理前 double-check 确认 VM 根盘确实不在旧存储上) + +**double-check 实现**:执行旧存储元数据删除前,查询当前根盘位置: + +```java +String currentRootPsUuid = Q.New(VolumeVO.class) + .eq(VolumeVO_.vmInstanceUuid, vmUuid) + .eq(VolumeVO_.type, VolumeType.Root) + .select(VolumeVO_.primaryStorageUuid) + .findValue(); + +if (oldPsUuid.equals(currentRootPsUuid)) { + // 根盘仍在旧存储(迁移可能已回滚),跳过删除 + logger.warn("VM {} root volume still on PS {}, skip metadata cleanup", vmUuid, oldPsUuid); + return; +} +// 安全删除旧存储上的元数据 +metadataStorageHandler.deleteMetadata(oldPsUuid, vmUuid, ...); +``` + +**竞态安全**:异步删除与迁移回滚的竞态窗口中,最坏情况是删除了正确位置的元数据。此时下一次 `@MetadataImpact` API 触发的 `markDirty` 会重新写入,不会永久丢失。 + +### 9.3 各存储类型实现 + +**sblk(共享块存储)** + +共享块存储场景使用完整的二进制协议,详见 [Part 4: sblk 二进制协议](vm-metadata-04-sblk.md)。 + +核心要点摘要: +- LV 命名:`{vm_uuid}_vmmeta`,路径 `/dev/{vg_uuid}/{vm_uuid}_vmmeta` +- 二进制格式:Header(512B) + Slot A + Slot B,A/B 双槽交替写入 +- 三阶段原子写入:Phase 1(标记 pending_op) → Phase 2(写 Slot) → Phase 3(切换 ActiveSlot + 清 pending_op) +- 初始大小 4MB,阶梯式扩容至最大 64MB +- **LV 初始化后立即写入空 DTO 到 Slot A**(避免首次读取返回 CORRUPTED 与真正损坏混淆) +- **LV 初始化时执行 O_DIRECT sanity check**(校验 I/O 路径正确性) + +**local/NFS** + +- 文件路径:与根盘同目录下的 `vm_metadata.json` +- 文件内容:DTO JSON 明文(其中每个资源的 systemTags/resourceConfigs 为 per-Resource Base64 编码) +- 原子写入:先写 tmp 文件,fsync 后 rename 替换 +- **完整性校验**:JSON 顶层增加 `_checksum` 字段,值为 `SHA-256(除 _checksum 外的所有 JSON 内容)`。读取时先校验 `_checksum`,不匹配则报错。写入时序列化 DTO → 计算 SHA-256 → 填入 `_checksum` → 写文件。成本极低(<100KB JSON SHA-256 耗时 <1ms),可检测静默位翻转和磁盘扇区错误 + +### 9.4 元数据生命周期 + +| 事件 | 行为 | +|------|------| +| 新创建虚拟机 | 自动创建元数据文件 | +| VM 删除 | 同步删除元数据文件 + 删除失败时异步重试(注:VM 级联删除 Volume/Snapshot 时,最终会删除 VM 元数据文件本身,无需单独更新元数据) | +| 存储迁移 | 新存储创建 → 旧存储异步清理 | +| 不支持的存储类型 | 静默跳过,不创建元数据文件 | + +**存储迁移时的元数据生命周期**: + +在 `VmStorageMigrateFlow` 中增加两步(均为 best-effort,失败不阻塞迁移): + +``` +step N-1: initializeMetadataOnTargetPS(vmUuid, targetPsUuid) + → 如果 PS 类型支持元数据,创建空 LV / 空 JSON 文件 + → 后续 Poller 刷写完整数据 + +step N: deleteMetadataOnSourcePS(vmUuid, sourcePsUuid) + → 删除旧 LV / 旧 JSON 文件 + → 失败 → 日志告警 + 健康巡检孤儿检测清理 +``` + +**迁移元数据创建失败的处理**:initializeMetadata 失败时只记告警,不阻塞迁移。后续 Poller 刷写时会自动创建(handler 检测到 LV 不存在 → 调用 initializeMetadata → 写入数据)。 + +### 9.5 不支持的存储类型 + +ceph、zbs、vhost 当前版本不支持元数据功能。处理逻辑: + +| 场景 | 行为 | +|------|------| +| 全局开关开启,VM 根盘在不支持的存储上 | 静默跳过,不创建元数据文件,不报错 | +| `@MetadataImpact` 拦截器触发时 | 检查根盘所在存储类型,不支持的存储类型直接跳过 markDirty | +| 注册 API 指定不支持的存储 | 返回错误 `METADATA_STORAGE_NOT_SUPPORTED` | + +### 9.6 MetadataStorageHandler 接口 + +不同存储类型的元数据读写操作通过统一接口抽象: + +```java +public interface MetadataStorageHandler { + /** 初始化元数据存储(创建 LV / 空 JSON 文件) */ + void initializeMetadata(String psUuid, String vmUuid, Completion completion); + + /** 删除元数据(删除 LV / JSON 文件) */ + void deleteMetadata(String psUuid, String vmUuid, Completion completion); + + /** 写入元数据(控制面构建好 payload,直接透传到 Agent 写入存储) */ + void writeMetadata(String psUuid, String vmUuid, String payloadJson, boolean storageStructureChange, Completion completion); + + /** + * 读取元数据(从存储读取并返回原始 JSON 字符串) + * @return payload JSON 字符串(控制面负责解析,Agent 不解析 DTO 内容) + */ + void readMetadata(String psUuid, String vmUuid, ReturnValueCompletion completion); + + /** 检查该存储类型是否支持元数据 */ + boolean isMetadataSupported(String psType); +} +``` + +> **重要设计约束**:Agent 端不解析 DTO 内容。控制面(Java 端)负责 DTO 的构建、序列化和反序列化。Agent 只负责将控制面传入的 payload 原样写入存储,或从存储读取原样返回。`storageStructureChange` 参数由控制面指定,sblk Agent 根据此参数设置三阶段写入的 `PendingOp` 字段值(见 [Part 4 §5.2](vm-metadata-04-sblk.md))。 + +| 实现类 | 存储类型 | initializeMetadata | writeMetadata | readMetadata | deleteMetadata | +|--------|---------|-------------------|---------------|--------------|----------------| +| `SblkMetadataStorageHandler` | SharedBlock | 创建 LV + 写初始 Header | 三阶段原子写入 LV | 读 Header + Active Slot | `lv_delete` | +| `LocalNfsMetadataStorageHandler` | Local/NFS | 创建空 JSON 文件 | tmp + fsync + rename | 读 JSON 文件 + checksum 校验 | `os.remove()` | + +--- + +## 10. 模板虚拟机(Templated VM)元数据 + +### 10.1 模板 VM 数据模型概述 + +模板虚拟机通过独立的 `TemplatedVmInstanceVO` 标记表来标识身份,与 `VmInstanceVO` 通过 uuid 外键 1:1 关联。模板 VM 的 `VmInstanceVO.type` 仍为 `"UserVm"`,无特殊 type。 + +``` +VmInstanceVO (type = "UserVm") + │ uuid = uuid (1:1, CASCADE) + ▼ +TemplatedVmInstanceVO ← 纯标记表,无额外业务字段 + ├── uuid (FK → VmInstanceEO) + ├── createDate + └── lastOpDate + +关联表(不纳入元数据): + TemplatedVmInstanceCacheVO ← 运行态:缓存 VM(从模板 Clone 而来) + TemplatedVmInstanceRefVO ← 追溯:记录从模板创建出的子 VM +``` + +### 10.2 元数据中的模板标识 + +`VmInstanceMetadataDTO.isTemplated` 字段记录 VM 是否为模板虚拟机。 + +**构建时**(`buildVmInstanceMetadata`): + +```java +dto.isTemplated = Q.New(TemplatedVmInstanceVO.class) + .eq(TemplatedVmInstanceVO_.uuid, vmInstanceUuid) + .isExists(); +``` + +**不纳入元数据的关联表及理由**: + +| VO | 是否纳入 | 理由 | +|----|---------|------| +| `TemplatedVmInstanceVO` | 通过 `isTemplated` boolean 代替 | 标记表无业务字段,`createDate`/`lastOpDate` 注册时重新生成 | +| `TemplatedVmInstanceCacheVO` | ❌ 不纳入 | 缓存 VM 是运行态产物(按需 Clone + 快照组),跨环境无意义,新环境首次从模板创建 VM 时自动创建 | +| `TemplatedVmInstanceRefVO` | ❌ 不纳入 | 子 VM 追溯关系属于旧环境,新环境不存在对应子 VM,引用无意义 | + +### 10.3 模板 VM 元数据更新时机 + +| 操作 | @MetadataImpact | 说明 | +|------|----------------|------| +| 普通 VM 转模板 VM | `CONFIG` | `isTemplated` false → true | +| 模板 VM 转回普通 VM | `CONFIG` | `isTemplated` true → false,同时删除 CacheVO/RefVO | +| 从模板创建子 VM | 不影响模板本身 | 子 VM 有独立的元数据 | +| 更新模板 VM 属性(CPU/内存/名称) | `CONFIG` | 通过 `UpdateVmInstanceMsg` 间接触发 | + +### 10.4 缓存 VM 的元数据处理 + +缓存 VM(`TemplatedVmInstanceCacheVO.cacheVmInstanceUuid`)有自己独立的 `VmInstanceVO`(type=UserVm)。 + +**处理策略**:缓存 VM 的元数据**不写入**。 + +理由: +- 缓存 VM 是内部运行态资源,用户不直接管理 +- 注册缓存 VM 无意义,它是从模板 Clone 的中间产物 +- 避免注册缓存 VM 时产生冗余的非模板 VM + +**实现**:`VmMetadataUpdateInterceptor` 中增加过滤逻辑: + +```java +// 如果 vmUuid 对应的是缓存 VM,跳过元数据更新 +boolean isCacheVm = Q.New(TemplatedVmInstanceCacheVO.class) + .eq(TemplatedVmInstanceCacheVO_.cacheVmInstanceUuid, vmUuid) + .isExists(); +if (isCacheVm) { + return; // 跳过 markDirty +} +``` + +### 10.5 schemaVersion 兼容性 + +`isTemplated` 是新增字段,需要 bump `schemaVersion`: + +- **新版本写入的元数据**:包含 `isTemplated` 字段 +- **旧版本写入的元数据**:不含该字段,Gson 反序列化时 `boolean` 默认值为 `false`(即非模板) +- **降级行为正确**:旧元数据注册时按非模板处理,符合预期 + +--- + +## 11. VmCdRomVO 等附属资源 + +当前版本不纳入元数据。理由: + +- CD-ROM 挂载状态通常不影响 VM 恢复启动 +- USB/PCI 透传设备与宿主机绑定,跨环境无意义 +- 后续版本如需支持,通过 `VmInstanceMetadataDTO` 新增字段 + bump schemaVersion + +--- + +## 12. API 接口设计 + +> **完整 API 文档已独立为 [Part 5: API 设计](vm-metadata-05-api.md)**,包含全部请求/响应定义、设计要点及合理性分析。本章仅保留汇总索引。 + +| API | HTTP | 类型 | 说明 | 详见 | +|-----|------|------|------|------| +| `APIGetVmInstanceMetadataFromPrimaryStorageMsg` | `GET /primary-storage/vm-instances/metadata` | 同步 | 列出存储上所有 VM 元数据概要 | [Part 5 §2](vm-metadata-05-api.md#2-获取主存储上的虚拟机元数据列表) | +| `APIReadVmInstanceMetadataFromPrimaryStorageMsg` | `GET /primary-storage/{psUuid}/vm-instances/{vmUuid}/metadata` | 同步 | 读取指定 VM 的完整元数据 JSON | [Part 5 §3](vm-metadata-05-api.md#3-获取指定虚拟机元数据详情) | +| `APIRegisterVmInstanceMsg` | `POST /vm-instances/register` | 异步 | 从元数据注册恢复虚拟机 | [Part 5 §4](vm-metadata-05-api.md#4-注册虚拟机) | +| `APICheckVmInstanceMetadataConsistencyMsg` | `PUT /vm-instances/{uuid}/consistency` | 异步 | 一致性检查(仅 CLI) | [Part 5 §5](vm-metadata-05-api.md#5-检查虚拟机元数据一致性) | +| `APIUpdateVmMetadataMsg` | `PUT /vm-instances/{uuid}/actions` | 异步 | 手动触发元数据全量更新(仅 CLI) | [Part 5 §6.1](vm-metadata-05-api.md#61-手动触发元数据更新) | +| `APIPreCheckVmMetadataRegistrationMsg` | `PUT /vm-instances/metadata/precheck` | 异步 | 注册预检查(仅 CLI) | [Part 5 §6.2](vm-metadata-05-api.md#62-注册预检查) | + +--- + +## 13. 新增/修改代码文件清单 + +### 13.1 新增文件 + +| 文件 | 位置 | 说明 | +|------|------|------| +| VmInstanceMetadataDTO.java | header/vm/ | 核心 DTO | +| VmInstanceMetadataCodec.java | header/vm/ | 编解码工具 | +| VmInstanceMetadataValidator.java | header/vm/ | 校验器 | +| VmInstanceMetadataConstants.java | header/vm/ | 常量定义 | +| VmInstanceMetadataRegistrationSpec.java | header/vm/ | 注册参数 | +| VmInstanceMetadataFieldProcessor.java | compute/vm/ | 注册字段处理器 | +| MetadataImpact.java | header/vm/ | API 影响类型注解 | +| VmUuidFromApiResolver.java | header/vm/ | vmUuid 解析接口 | +| VmMetadataDirtyVO.java | header/vm/ | 脏标记 VO(替代 GC 框架,见 [Part 2](vm-metadata-02-dirty-mark.md)) | +| MetadataDirtyPoller.java | compute/vm/ | 周期轮询刷写任务 | +| UpdateVmInstanceMetadataMsg.java | header/vm/ | MN 内部消息 | +| UpdateVmInstanceMetadataReply.java | header/vm/ | MN 内部消息回复 | +| UpdateVmInstanceMetadataOnPrimaryStorageMsg.java | header/vm/ | 主存储消息 | +| MetadataStorageHandler.java | header/vm/ | 存储层元数据操作接口(§9.6) | +| MetadataTagPattern.java | header/vm/ | SystemTag 白名单模式定义 | +| MetadataWhitelistProvider.java | header/vm/ | SystemTag/ResourceConfig/API 白名单注册接口 | +| MetadataWhitelistChecker.java | test/ | CI 编译期统一白名单检查(@MetadataImpact + SystemTag + ResourceConfig) | +| MetadataTagAnnotationChecker.java | test/ | CI 编译期 SystemTag 标注检查 | +| RegistrationCleanupJob.java | compute/vm/ | 注册崩溃残留清理任务 | +| BatchCheckMetadataStatusMsg.java | header/vm/ | 批量检查元数据 Header 状态 | +| RepairMetadataMsg.java | header/vm/ | 修复元数据 Header(512B 写入) | +| VmInstanceMetadataStruct.java | header/storage/primary/ | 元数据概要结构体(§12.1) | +| APIGetVmInstanceMetadataFromPrimaryStorageMsg.java | header/storage/primary/ | 获取元数据列表 API(§12.1) | +| APIGetVmInstanceMetadataFromPrimaryStorageReply.java | header/storage/primary/ | 获取元数据列表响应 | +| APIReadVmInstanceMetadataFromPrimaryStorageMsg.java | header/storage/primary/ | 读取指定 VM 元数据 API(§12.2) | +| APIReadVmInstanceMetadataFromPrimaryStorageReply.java | header/storage/primary/ | 读取指定 VM 元数据响应 | +| APIRegisterVmInstanceMsg.java | header/storage/primary/ | 注册虚拟机 API(§12.3) | +| APIRegisterVmInstanceEvent.java | header/storage/primary/ | 注册虚拟机响应事件 | +| APICheckVmInstanceMetadataConsistencyMsg.java | header/vm/ | 一致性检查 API(§12.4) | +| APICheckVmInstanceMetadataConsistencyEvent.java | header/vm/ | 一致性检查响应事件 | +| APIUpdateVmMetadataMsg.java | header/vm/ | 手动更新元数据 API(§12.5.1) | +| APIPreCheckVmMetadataRegistrationMsg.java | header/vm/ | 注册预检查 API(§12.5.2) | + +### 13.2 修改文件 + +| 文件 | 修改内容 | +|------|----------| +| VmMetadataUpdateInterceptor.java | 使用 `markDirty()` 替代 GC submit,统一 NAME 格式 | +| VmInstanceBase.java | handler 加 ChainTask 串行化;消息改 makeLocalServiceId | +| CloudBusImpl3.java | 预留扩展(当前无需修改) | diff --git a/docs/design/vm-metadata-02-dirty-mark.md b/docs/design/vm-metadata-02-dirty-mark.md new file mode 100644 index 00000000000..f6359da3373 --- /dev/null +++ b/docs/design/vm-metadata-02-dirty-mark.md @@ -0,0 +1,883 @@ +# Dirty Mark + Poller + +# 1. 概述 + +## 1.1 问题:GC 框架的结构性错配 + +GC 框架是 **"一个任务对应一行 DB 记录"** 的模型。每次 API 成功都 `submit()` 创建新 GC 行,通过 ChainTask `maxPendingTasks=1` + `exceedMaxPendingCallback` 淘汰多余行。这导致: + +| 问题 | 说明 | +|------|------| +| **GC 行爆炸** | 100 个 API → 100 行 GC,98 行立即 Done,需定期清理 | +| **deduplicateSubmit 不可用** | GC 执行期间 status 仍为 Idle,新 GC 被误判"已有在处理" | +| **双 MN 复杂度** | 需 hash 环路由 SubmitGCMsg + 执行层 delegation + reply 回退,6 种极端情况需逐一分析 | +| **delegation 消耗 retryCount** | 非 owner 上 triggerNow 的 delegation 失败也递增 retryCount | +| **框架修改** | 需修改 loadOrphanJobs 增加状态过滤、需新增索引 | + +**根因**:元数据更新需要的是 **"标脏 → 合并 → 刷写"** 模型(多次修改合并为一次写入),而非 GC 的 **"一个失败任务 → 一次重试"** 模型。 + +## 1.2 新方案一句话 + +> 用一张 **`VmMetadataDirtyVO`** 表做脏标记(一个 VM 最多一行),**`PeriodicTask`** 轮询器定期认领并刷写,成功删行,失败释放等下轮。 + +灵感来源:`SecurityGroupFailureHostVO` + `FailureHostWorker` 模式。 + +## 1.3 核心不变量 + +- 刷写时始终从 DB 查询 VM 完整当前状态构建 payload,不使用触发 API 时的增量数据。 +- 任何一次刷写完成后,存储上的元数据反映数据库最新完整状态。 +- `buildVmInstanceMetadata()` 必须标注 `@Transactional(readOnly = true)`,MySQL InnoDB REPEATABLE READ 事务内所有查询使用同一快照,保证单次构建的读一致性。`readOnly = true` 不启动写事务,开销极小。 + +## 1.4 最终一致性模型 + +`buildVmInstanceMetadata()` 读 DB 到 `pwrite` 完成之间存在毫秒级窗口,期间其他 API 可能修改了 DB(如删除快照)。此时写入的元数据可能包含已过期信息。这不是问题——修改 DB 的 API 成功后会再次 `markDirty()`,下轮 Poller 从 DB 全量读取已反映最新状态,覆盖写入自然修正。 + +对注册场景,Part 3 §3.4 的 installPath 存在性检查提供额外兜底。 + +--- + +# 2. 数据模型 + +## 2.1 VmMetadataDirtyVO + +```java +@Entity +@Table +public class VmMetadataDirtyVO { + @Id + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE) + private String vmInstanceUuid; // 主键 = 天然去重 + + @Column + @ForeignKey(parentEntityClass = ManagementNodeVO.class, onDeleteAction = ReferenceOption.SET_NULL) + private String managementNodeUuid; // null = 未认领,非null = 已认领 + + @Column + private long dirtyVersion; // 每次 markDirty 递增,用于检测刷写期间的新变更 + + @Column + private boolean storageStructureChange; // 是否涉及存储结构变更(OP type 标记) + + @Column + private int retryCount; // 连续失败次数 + + @Column + private Timestamp nextRetryTime; // 下次可被认领的时间(退避控制) + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; // 最后一次 markDirty 的时间(关键!) +} +``` + +**关键设计决策**: + +| 设计点 | 决策 | 原因 | +|--------|------|------| +| `vmInstanceUuid` 做主键 | 一个 VM 最多一行 | 天然去重,100 个 API 只产生 1 行,不是 100 行 | +| `managementNodeUuid` FK SET_NULL | MN 宕机自动释放 | 无需额外孤儿扫描,DB 约束自动完成 | +| `vmInstanceUuid` FK CASCADE | VM 销毁自动删除脏标记 | 无残留 | +| `dirtyVersion` | 每次 markDirty +1 | 刷写前快照 version,成功后比较——检测刷写期间是否有新变更(见 §4.5)。语义比时间戳比较更明确,无精度问题 | +| `storageStructureChange` | OR 升级策略 | `@MetadataImpact(CONFIG)` → false(OP type 1),`@MetadataImpact(STORAGE)` → true(OP type 2)。多次 markDirty 取 OR:一旦标记为 STORAGE 则本轮不降级 | +| `lastOpDate` | MySQL 自动更新 | Poller 认领时排序依据(最早变更优先处理) | +| `nextRetryTime` | 退避控制 | 失败后不立刻重试,等到下次重试时间 | + +## 2.2 DDL + +```sql +CREATE TABLE VmMetadataDirtyVO ( + vmInstanceUuid VARCHAR(32) NOT NULL, + managementNodeUuid VARCHAR(32) DEFAULT NULL, + dirtyVersion BIGINT NOT NULL DEFAULT 1, + storageStructureChange TINYINT(1) NOT NULL DEFAULT 0, + retryCount INT NOT NULL DEFAULT 0, + nextRetryTime TIMESTAMP NULL DEFAULT NULL, + createDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + lastOpDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (vmInstanceUuid), + CONSTRAINT fkVmMetadataDirtyVOVmInstanceEO FOREIGN KEY (vmInstanceUuid) + REFERENCES VmInstanceEO (uuid) ON DELETE CASCADE, + CONSTRAINT fkVmMetadataDirtyVOManagementNodeVO FOREIGN KEY (managementNodeUuid) + REFERENCES ManagementNodeVO (uuid) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +## 2.3 推荐索引 + +```sql +-- Poller CAS 认领查询: WHERE managementNodeUuid IS NULL AND nextRetryTime <= NOW() +CREATE INDEX idx_dirty_unclaimed ON VmMetadataDirtyVO (managementNodeUuid, nextRetryTime); +``` + +> 与 GarbageCollectorVO 的详细对比见 [对比文档 §1](2/vm-metadata-new-02h-compare.md#1-数据模型对比vmmetadatadirtyvo-vs-garbagecollectorvo)。 + +--- + +# 3. markDirty — 标脏入口 + +## 3.1 核心逻辑 + +```java +public void markDirty(String vmInstanceUuid, boolean storageStructureChange) { + // 前置检查:功能开关 + if (!VmGlobalConfig.VM_METADATA_ENABLED.value(Boolean.class)) { + return; + } + + // INSERT ... ON DUPLICATE KEY UPDATE dirtyVersion = dirtyVersion + 1 + // 若行不存在 → INSERT(dirtyVersion=1, managementNodeUuid=null, retryCount=0) + // 若行已存在 → dirtyVersion +1(标记"有新变更") + // storageStructureChange 使用 OR 升级:一旦标 STORAGE 则不降级 + // 不重置 retryCount(保留退避状态) + // 不修改 managementNodeUuid(不干扰正在执行的刷写) + // 不修改 nextRetryTime(不干扰退避计时) + SQL.New("INSERT INTO VmMetadataDirtyVO (vmInstanceUuid, dirtyVersion, storageStructureChange) " + + "VALUES (:vmUuid, 1, :ssc) " + + "ON DUPLICATE KEY UPDATE dirtyVersion = dirtyVersion + 1, " + + "storageStructureChange = storageStructureChange OR VALUES(storageStructureChange)") + .param("vmUuid", vmInstanceUuid) + .param("ssc", storageStructureChange) + .execute(); + + // 立即唤醒:尝试认领并提交刷写,不等待 Poller 轮询 + triggerFlushForVm(vmInstanceUuid); +} + +/** + * 便捷重载:默认 storageStructureChange=false(CONFIG 级别)。 + */ +public void markDirty(String vmInstanceUuid) { + markDirty(vmInstanceUuid, false); +} +``` + +### 为什么 markDirty 需要检查 `vm.metadata.enabled`? + +> **讨论结论**:需要检查。虽然 `VmMetadataUpdateInterceptor` 层已检查功能开关,但 markDirty 还有其他调用方(级联删除、HA 回调、巡检恢复等),这些调用方未必都做了检查。在 markDirty 内统一检查是防御性编程的最低成本方案。 + +### 为什么不重置 retryCount? + +如果 PS 持续不可用,连续 API 触发的 markDirty 不应重置重试计数器,否则永远不会触达上限告警。retryCount 仅在**刷写成功**时重置为 0。 + +### 为什么不修改 managementNodeUuid? + +若 Poller 已认领此行正在刷写,markDirty 不应抢走它。`dirtyVersion` 递增后,刷写完成时会通过版本号比较发现“有新变更”,自动释放让下轮重处理(见 §4.5)。 + +### markDirty 后立即唤醒 + +> **讨论结论**:markDirty 后立即调用 `triggerFlushForVm(vmUuid)` 尝试认领并提交刷写,消除最长 5s 的 Poller 等待延迟。Poller 降级为**安全网**,负责处理:退避中的行、MN 宕机后释放的行、triggerFlush 未能认领的行。 + +```java +/** + * 立即尝试认领并刷写指定 VM 的 dirty 行。 + * 若行已被认领或处于退避期,跳过(Poller 安全网会处理)。 + */ +private void triggerFlushForVm(String vmUuid) { + int claimed = SQL.New("UPDATE VmMetadataDirtyVO " + + "SET managementNodeUuid = :myId " + + "WHERE vmInstanceUuid = :vmUuid " + + "AND managementNodeUuid IS NULL " + + "AND (nextRetryTime IS NULL OR nextRetryTime <= CURRENT_TIMESTAMP)") + .param("myId", Platform.getManagementServerId()) + .param("vmUuid", vmUuid) + .execute(); + + if (claimed == 0) return; // 已被认领 or 退避中 → Poller 处理 + + VmMetadataDirtyVO dirty = dbf.findByUuid(vmUuid, VmMetadataDirtyVO.class); + if (dirty == null) return; + + submitFlushTask(dirty); // 提交到 ChainTask(同 Poller 路径) +} +``` + +> **退避中的行不会被立即唤醒**:若 dirty 行处于指数退避(`nextRetryTime > NOW()`),triggerFlush 的 WHERE 条件将其排除。这是有意设计——退避意味着 PS 可能不可用,markDirty 带来的新变更会在退避到期后由 Poller 一并处理。 + +## 3.2 调用位置 + +| 调用方 | 场景 | 说明 | +|--------|------|------| +| `VmMetadataUpdateInterceptor.beforePublishEvent()` | `@MetadataImpact` API 成功后 | 主流程 | +| `MetadataCascadeExtension.asyncCascade()` | 级联删除 Volume/Snapshot | 非 API 内部操作 | +| HA handler 完成回调 | HA 重启 VM | 非 API 内部操作 | +| 定时快照清理 handler | 快照删除 | 非 API 内部操作 | +| 内部卷迁移 handler | installPath 变更 | 非 API 内部操作 | +| 升级全量刷新 | 版本变更后批量触发 | 见 §9 | + +**两道防线**: + +1. **开发规范**:修改 VM 存储拓扑字段的内部消息处理器,成功后必须调用 `markDirty()` +2. **健康巡检兜底**(暂缓):后续版本可引入周期巡检全量比对 DB vs 存储元数据,发现不一致则 `markDirty()` 触发 full-refresh + +> 对注册场景,即使元数据暂时落后于 DB,Part 3 §3.4 的 installPath 存在性检查提供额外兜底。 + +> 与 GC 方案 submit 的详细对比见 [对比文档 §2](2/vm-metadata-new-02h-compare.md#2-标脏入口对比markdirty-vs-gc-submit)。 + +--- + +# 4. MetadataDirtyPoller — 轮询刷写 + +## 4.1 基本结构 + +```java +public class MetadataDirtyPoller implements PeriodicTask { + @Override + public TimeUnit getTimeUnit() { return TimeUnit.SECONDS; } + + @Override + public long getInterval() { + return VmGlobalConfig.VM_METADATA_DIRTY_POLL_INTERVAL.value(Long.class); + // 默认 5 秒,可通过 GlobalConfig 动态调整 + } + + @Override + public String getName() { return "vm-metadata-dirty-poller"; } + + @Override + public void run() { + claimAndFlush(); + } +} +``` + +启动:在 `managementNodeReady()` 中 `thdf.submitPeriodicTask(new MetadataDirtyPoller())`。 + +GlobalConfig 变更时自动重启 Poller(与 SecurityGroup FailureHostWorker 一致): + +```java +VmGlobalConfig.VM_METADATA_DIRTY_POLL_INTERVAL.installUpdateExtension((oldValue, newValue) -> { + restartPoller(); +}); +``` + +> **Poller 角色定位**:markDirty 后立即调用 `triggerFlushForVm()` 已覆盖常规场景(见 §3.1)。Poller 降级为**安全网**,负责处理: +> - 退避中的行(`nextRetryTime` 到期后才能认领) +> - MN 宕机后 FK SET_NULL 释放的孤儿行 +> - triggerFlushForVm 认领失败的行(已被其他 MN Poller 认领) + +## 4.2 认领(CAS 方式) + +> **讨论结论**:采用 CAS(单条 UPDATE WHERE NULL LIMIT N),比悲观锁更简洁,避免死锁风险。 + +```java +private List claimDirtyRows() { + // Step 1: CAS 原子认领 — 单条 UPDATE 天然原子 + String sql = "UPDATE VmMetadataDirtyVO " + + "SET managementNodeUuid = :myId " + + "WHERE managementNodeUuid IS NULL " + + "AND (nextRetryTime IS NULL OR nextRetryTime <= CURRENT_TIMESTAMP) " + + "ORDER BY lastOpDate ASC " + // 最早变更的优先 + "LIMIT :batchSize"; + + int claimed = SQL.New(sql) + .param("myId", Platform.getManagementServerId()) + .param("batchSize", VmGlobalConfig.VM_METADATA_DIRTY_BATCH_SIZE.value(Integer.class)) + .execute(); + + if (claimed == 0) return Collections.emptyList(); + + // Step 2: 查询刚认领到的行 + return Q.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.managementNodeUuid, Platform.getManagementServerId()) + .list(); +} +``` + +**CAS vs 悲观锁**: + +| | CAS (UPDATE WHERE NULL) | 悲观锁 (SELECT FOR UPDATE) | +|---|---|---| +| 原子性 | 单条 UPDATE 天然原子 | 需事务包裹 SELECT + UPDATE | +| 死锁风险 | 无 | 双 MN 可能死锁 | +| 性能 | 无锁等待 | 有锁等待 | +| 实现复杂度 | 低 | 中 | + +## 4.3 刷写(Flush) + +认领成功后,对每个 dirty row 提交到 ChainTask 执行刷写: + +```java +private void claimAndFlush() { + List claimed = claimDirtyRows(); + for (VmMetadataDirtyVO dirty : claimed) { + submitFlushTask(dirty); + } +} + +private void submitFlushTask(VmMetadataDirtyVO dirty) { + // 外层全局限流 + thdf.chainSubmit(new ChainTask(null) { + @Override + public String getSyncSignature() { + return "update-vm-metadata-global"; + } + @Override + public int getSyncLevel() { + return VmGlobalConfig.VM_METADATA_GLOBAL_MAX_CONCURRENT.value(Integer.class); + } + + @Override + public void run(SyncTaskChain outerChain) { + // 内层 per-VM 串行 + 去重 + thdf.chainSubmit(new ChainTask(null) { + @Override + public String getSyncSignature() { + return "update-vm-" + dirty.getVmInstanceUuid() + "-metadata"; + } + @Override + public int getSyncLevel() { return 1; } + @Override + public int getMaxPendingTasks() { return 1; } + @Override + public String getDeduplicateString() { return getSyncSignature(); } + + @Override + public void exceedMaxPendingCallback() { + // 已有 running + pending,本次多余 → 释放认领 + releaseClaim(dirty.getVmInstanceUuid()); + outerChain.next(); + } + + @Override + public void run(SyncTaskChain innerChain) { + doFlush(dirty, () -> { + innerChain.next(); + outerChain.next(); + }); + } + }); + } + }); +} +``` + +## 4.4 doFlush 核心逻辑 + +```java +private void doFlush(VmMetadataDirtyVO dirty, Runnable chainNext) { + String vmUuid = dirty.getVmInstanceUuid(); + + // 0. 记录刷写开始时的 dirtyVersion 快照 + long snapshotVersion = dirty.getDirtyVersion(); + + // 1. 前置检查:VM 是否存在 + if (!dbf.isExist(vmUuid, VmInstanceVO.class)) { + // VM 已删除,FK CASCADE 应已删除 dirty 行,兜底删除 + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid).delete(); + chainNext.run(); + return; + } + + // 2. 发送 UpdateVmInstanceMetadataMsg → VmInstanceBase 负责构建 payload 并写入主存储 + // payload 构建(buildVmInstanceMetadata)和大小保护均在 VmInstanceBase 内部完成 + UpdateVmInstanceMetadataMsg msg = new UpdateVmInstanceMetadataMsg(); + msg.setUuid(vmUuid); + msg.setStorageStructureChange(dirty.isStorageStructureChange()); + msg.setTimeout(TimeUnit.MINUTES.toMillis(2)); + bus.makeLocalServiceId(msg, VmInstanceConstant.SERVICE_ID); + + bus.send(msg, new CloudBusCallBack(null) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + onFlushSuccess(vmUuid, snapshotVersion); + } else { + onFlushFailure(vmUuid, reply.getError()); + } + chainNext.run(); + } + }); +} +``` + +## 4.5 刷写成功处理 + +```java +private void onFlushSuccess(String vmUuid, long snapshotVersion) { + // 条件删除:仅当 dirtyVersion == snapshotVersion 时删除 + // 即"刷写期间没有新的 markDirty 到来" + int deleted = SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .eq(VmMetadataDirtyVO_.dirtyVersion, snapshotVersion) + .delete(); + + if (deleted == 0) { + // dirtyVersion > snapshotVersion → 刷写期间有新变更(markDirty 递增了 dirtyVersion) + // 释放认领,让 triggerFlush / Poller 重新处理 + // 同时重置 retryCount(本次成功说明通路正常) + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) + .set(VmMetadataDirtyVO_.retryCount, 0) + .set(VmMetadataDirtyVO_.nextRetryTime, null) + .update(); + } + // deleted > 0 → 行已删除,彻底完成 +} +``` + +**这是整个方案最关键的设计点**。`dirtyVersion` 比较确保不会丢失刷写期间产生的新变更: + +``` +T0: markDirty(vm-1) → INSERT, dirtyVersion=1 +T1: 认领 → snapshotVersion=1 +T2: 刷写进行中... buildVmInstanceMetadata() 读到 v1 +T3: API 成功 → markDirty(vm-1) → dirtyVersion=2 ← 新变更! +T4: 刷写完成,写入 v1 +T5: onFlushSuccess → DELETE WHERE dirtyVersion = 1 + → 当前 dirtyVersion=2 ≠ 1 → deleted=0 + → 释放认领 → triggerFlush 立即重处理 → 读到 v2 → 写入 v2 ✓ +``` + +如果不做 `dirtyVersion` 比较直接删除,T3 的变更就丢了——这正是 GC `deduplicateSubmit` 遇到的同类问题,新方案用版本号比较优雅解决。相比 `lastOpDate` 时间戳比较,`dirtyVersion` 整数比较语义更明确、无时间精度问题。 + +## 4.6 刷写失败处理 + +```java +private void onFlushFailure(String vmUuid, ErrorCode error) { + VmMetadataDirtyVO dirty = dbf.findByUuid(vmUuid, VmMetadataDirtyVO.class); + if (dirty == null) return; // VM 已销毁,FK CASCADE 已清理 + + int newRetryCount = dirty.getRetryCount() + 1; + int maxRetry = VmGlobalConfig.VM_METADATA_MAX_RETRY.value(Integer.class); // 默认 5 + + if (newRetryCount >= maxRetry) { + // 达到上限 → 告警 + 删除行 + // 下次该 VM 的 @MetadataImpact API 成功时会重新 markDirty,自然重试 + logger.error("metadata update for vm {} failed after {} retries, giving up. " + + "Will auto-retry on next API that modifies this VM.", + vmUuid, newRetryCount); + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid).delete(); + return; + } + + // 未达上限 → 释放认领 + 指数退避 + long delaySec = BASE_DELAY_SECONDS * (1L << Math.min(newRetryCount, MAX_EXPONENT)); + // BASE_DELAY_SECONDS=10, MAX_EXPONENT=10 + Timestamp nextRetry = Timestamp.from(Instant.now().plusSeconds(delaySec)); + + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) // 释放认领 + .set(VmMetadataDirtyVO_.retryCount, newRetryCount) + .set(VmMetadataDirtyVO_.nextRetryTime, nextRetry) + .update(); + + logger.warn("metadata update for vm {} failed (retry {}/{}), next retry at {}", + vmUuid, newRetryCount, maxRetry, nextRetry); +} +``` + +**指数退避表**: + +| 尝试次数 | retryCount 变化 | 下次退避延迟 | 累计耗时 | +|----------|-----------------|-------------|----------| +| 1 | 0 → 1 | 20s | ~25s | +| 2 | 1 → 2 | 40s | ~65s | +| 3 | 2 → 3 | 80s | ~145s | +| 4 | 3 → 4 | 160s | ~305s | +| 5 | 4 → 5 | — | 放弃 | + +> 延迟公式:`BASE_DELAY_SECONDS × 2^min(retryCount, MAX_EXPONENT)`,其中 `BASE_DELAY_SECONDS = 10`,`MAX_EXPONENT = 10`。默认 5 次重试,总耗时约 5 分钟后放弃。 + +## 4.7 辅助方法 + +```java +private void releaseClaim(String vmUuid) { + SQL.New(VmMetadataDirtyVO.class) + .eq(VmMetadataDirtyVO_.vmInstanceUuid, vmUuid) + .set(VmMetadataDirtyVO_.managementNodeUuid, null) + .update(); +} +``` + +--- + +# 5. 消息调用链 + +## 5.1 新调用链 + +``` +API (e.g. StartVmInstanceMsg) 成功 + ↓ +VmMetadataUpdateInterceptor.beforePublishEvent() + ↓ +markDirty(vmUuid) ← INSERT/UPDATE + dirtyVersion++,本地操作,无跨 MN + ↓ +triggerFlushForVm(vmUuid) ← 立即唤醒:CAS 认领单行 + 提交 ChainTask + ↓(认领失败时由 Poller 安全网兆底,≤5s) + ↓ 外层 ChainTask "update-vm-metadata-global" (syncLevel=N) + ↓ 内层 ChainTask "update-vm-{vmUuid}-metadata" (maxPending=1) + ↓ +doFlush() + → bus.send(UpdateVmInstanceMetadataMsg) → makeLocalServiceId + ↓ +VmInstanceBase.handle(UpdateVmInstanceMetadataMsg) + → buildVmInstanceMetadata(vmUuid) — DB 全量读取(@Transactional(readOnly=true)) + → payload 大小保护(>8MB 告警, >30MB 拒绝) + ↓ +bus.send(UpdateVmInstanceMetadataOnPrimaryStorageMsg) → makeLocalServiceId + ↓ +NFS/LocalStorage/SharedBlock.handle() + ↓ ChainTask "update-metadata-on-ps-{psUuid}" + ↓ 选取 Host → UpdateVmInstanceMetadataOnHypervisorMsg + ↓ makeTargetServiceIdByResourceUuid(hostUuid) ← 保留 hash 环路由 + ↓ +HostBase.handle() → HTTP call to KVM agent + ↓ +成功 → onFlushSuccess() → 条件 DELETE +失败 → onFlushFailure() → 指数退避释放 +``` + +> **OP type 由管理层面指定**:`@MetadataImpact(CONFIG)` → OP type=1(仅配置变更),`@MetadataImpact(STORAGE)` → OP type=2(存储拓扑变更,sblk 场景设置 pending_op=2)。OP type 通过 `storageStructureChange` 字段贯穿整条消息链(`VmMetadataDirtyVO` → `UpdateVmInstanceMetadataMsg` → `UpdateVmInstanceMetadataOnPrimaryStorageMsg` → `UpdateVmInstanceMetadataOnHypervisorMsg`)。dirty 行使用 OR 升级策略:多次 markDirty 中只要有一次是 STORAGE,本轮刷写即使用 OP type=2。 + +> **消息超时**:`UpdateVmInstanceMetadataMsg` 和 `UpdateVmInstanceMetadataOnHypervisorMsg` 均 `setTimeout(2min)`,防止大 payload 的 O_DIRECT 写入 + 可能的 lvextend 操作超出默认消息超时。 + +> 与 GC 方案消息链的详细对比见 [对比文档 §3](2/vm-metadata-new-02h-compare.md#3-消息调用链对比)。 + +## 5.2 消息路由策略 + +| 消息 | 路由方式 | 说明 | +|------|----------|------| +| `UpdateVmInstanceMetadataMsg` | `makeLocalServiceId` | Poller 本地发起 | +| `UpdateVmInstanceMetadataOnPrimaryStorageMsg` | `makeLocalServiceId` | 无本地状态依赖 | +| `UpdateVmInstanceMetadataOnHypervisorMsg` | `makeTargetServiceIdByResourceUuid(hostUuid)` | 需路由到 host-owner MN | + +--- + +# 6. 并发控制(三层) + +## 6.1 三层串行化保证 + +``` +Layer 1 — DB CAS 认领 + UPDATE WHERE managementNodeUuid IS NULL → 同一行只被一个 MN 处理 + ⇒ 同一 VM 的刷写不会在两个 MN 上同时执行 + +Layer 2 — ChainTask 队列 "update-vm-{vmUuid}-metadata" + syncLevel=1, maxPendingTasks=1 + ⇒ 同一 VM 最多 1 个正在执行 + 1 个排队 + ⇒ 超出时 exceedMaxPendingCallback() 释放认领 + +Layer 3 — 主存储级队列 "update-metadata-on-ps-{psUuid}" + syncLevel = vm.metadata.ps.maxConcurrent (GlobalConfig, 默认 5) + ⇒ 同一 MN 上,同一存储最多 N 个并发写入 + ⇒ 双 MN 环境下实际全局并发 = 2 × syncLevel +``` + +> 与 GC 方案并发控制的详细对比见 [对比文档 §4](2/vm-metadata-new-02h-compare.md#4-并发控制对比)。 + +## 6.2 全局限流 + +嵌套 ChainTask 结构: + +``` +外层: syncSignature = "update-vm-metadata-global" + syncLevel = vm.metadata.global.maxConcurrent (默认 10) + 内层: syncSignature = "update-vm-{vmUuid}-metadata" + syncLevel = 1, maxPendingTasks = 1, deduplicateString = syncSignature +``` + +- 外层控制全局并发数,每个 MN 最多 N 个 VM 同时更新 +- 内层保证 per-VM 串行 + 去重 +- 两层都是 JVM 本地 ChainTask,无跨 MN 开销 + +> **per-MN 语义**:外层 `syncLevel` 是 JVM 本地限制。双 MN 环境下实际全局并发最大为 `2 × syncLevel`。DB CAS 认领已保证同一 VM 不会在两个 MN 上同时执行,全局并发 2N 对存储层压力可控(Layer 3 per-PS 限流进一步约束)。 + +## 6.3 Layer 3 实现位置 + +各主存储 `handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg)` 内部用 `thdf.chainSubmit()` 包装: + +- `getSyncSignature()` → `"update-metadata-on-ps-" + self.getUuid()` +- `getSyncLevel()` → 读取 `VmGlobalConfig.VM_METADATA_PS_MAX_CONCURRENT` +- `run()` → 调用实际写入逻辑后 `chain.next()` + +**外层全局队列与 Layer 3 的交互**:外层全局队列 `syncLevel=10` 限制单个 MN 上同时最多 10 个 VM 的元数据更新在执行。这 10 个并发任务分布在不同主存储上时,Layer 3 per-PS 队列 `syncLevel=5` 进一步约束同一存储的并发数。外层控制"总水位",Layer 3 控制"每个 PS 的分水位",二者嵌套生效。 + +--- + +# 7. 双 MN 高可用 + +## 7.1 为什么不需要 hash 环路由 + +`VmMetadataDirtyVO` 是 **共享 DB 表**,两个 MN 的 Poller 都能看到。认领通过 **DB CAS** 保证互斥,不依赖 JVM 本地状态——谁先认领谁处理,无需协调"谁是 owner"。 + +## 7.2 MN 宕机场景(自动恢复) + +``` +T0: MN-A Poller 认领 dirty(vm-1) + DB: {vmUuid:vm-1, managementNodeUuid:MN-A} + +T1: MN-A 宕机 + +T2: MN-B 心跳检测 → 删除 ManagementNodeVO(MN-A) + FK ON DELETE SET NULL → dirty(vm-1).managementNodeUuid = NULL + ← DB 约束自动完成,无需任何代码! + +T3: MN-B nodeLeft(MN-A) → 立即触发一轮 Poller + → 发现 vm-1 未认领 → CAS 认领 → 刷写 ✓ +``` + +**接管延迟**:心跳超时(~30s) + nodeLeft 立即触发 ≈ **~30 秒** + +> **讨论结论**:增加 `nodeLeft` 回调加速。实现 `ManagementNodeChangeListener`,在 `nodeLeft()` 时立即调用 `claimAndFlush()`,不等待下一个 Poller 周期。 + +```java +@Override +public void nodeLeft(ManagementNodeInventory inv) { + // MN 宕机 → FK SET_NULL 已释放其认领的 dirty 行 + // 立即触发一轮 Poller,尽快接管 + claimAndFlush(); +} + +@Override +public void nodeJoin(ManagementNodeInventory inv) { + // 无需特殊处理,新 MN 的 Poller 正常启动即可 +} + +@Override +public void iAmDead(ManagementNodeInventory inv) { + // 本 MN 即将死亡,不做处理 + // FK SET_NULL 会自动释放本 MN 认领的行 +} + +@Override +public void iJoin(ManagementNodeInventory inv) { + // 由 managementNodeReady 启动 Poller +} +``` + +**接管延迟**:心跳超时(~30s) + nodeLeft 立即触发 ≈ **~30 秒**。 + +## 7.3 MN 加入场景(无影响) + +``` +T0: MN-A 独自运行,Poller 认领并处理所有 dirty 行 +T1: MN-B 加入 +T2: MN-B Poller 启动 → 与 MN-A Poller 并行运行 + → 两个 Poller 竞争认领 → DB CAS 保证互斥 → 自然负载均衡 +``` + +无需任何特殊处理。两个 Poller 天然分摊工作。 + +## 7.4 双 MN 负载分配 + +两个 MN 的 Poller 并行运行,通过 DB CAS 自然竞争: + +- CAS `UPDATE ... WHERE managementNodeUuid IS NULL LIMIT N` → 每个 MN 各抢到一部分 +- 负载分配取决于 Poller 执行时机,不保证精确 50/50 + +> 通常不需要精确均匀分配。如需更均匀可在 claim 查询中按 vmUuid 分片(`vmUuid % 2 = mnIndex`),但这引入了对 MN 数量的依赖,不推荐。 + +## 7.5 时序验证 + +### 正常态 + +``` +MN-A: API 成功 → markDirty(vm-1) → INSERT dirty 行 +MN-B: Poller → CAS claim → flush → 成功 → DELETE ✓ +→ 任何一个 MN 都可以处理任何 VM 的 dirty 行 ✓ +``` + +### MN 宕机 + +``` +T0: MN-A claim dirty(vm-1), 正在刷写 +T1: MN-A 宕机 +T~30: MN-B 心跳检测 → 删除 ManagementNodeVO(A) + → FK SET_NULL → dirty(vm-1).managementNodeUuid = NULL +T~30: MN-B nodeLeft(A) → 立即触发 claimAndFlush() + → CAS claim vm-1 → flush → 成功 → DELETE ✓ +``` + +### MN 加入 + +``` +T0: MN-A 独自运行,处理所有 dirty +T1: MN-B 加入 → Poller 启动 +T6: MN-A Poller: claim 3 rows → flush + MN-B Poller: claim 2 rows → flush + → 自然分摊 ✓ +``` + +--- + +# 8. 管理平面恢复策略 + +恢复策略表: + +| 触发源 | 检测方式 | 管理平面行为 | +|--------|---------|-------------| +| 刷写达到重试上限 | `onFlushFailure()` | 告警日志 + 删除 dirty 行(下次 API 自动重试) | +| read 返回 NEED_REPAIR | 巡检/读取时 | `RepairMetadataMsg`(512B Header 写) | +| read 返回 CORRUPTED | 巡检/读取时 | `markDirty(vmUuid)`(全量重写) | +| read 返回 STORAGE_CHANGE_INCOMPLETE | 巡检/读取时 | `markDirty(vmUuid)` | +| VG 空间不足 | Agent 返回错误码 | 告警 + 退避 + 巡检重试 | +| 注册崩溃残留 | MN 启动/定时扫描 | Saga 回滚(5 条件判断) | +| 存储迁移失败 | 迁移 post-hook | 告警 + markDirty 自愈 | +| VM 销毁残留 | 销毁 post-hook + 巡检 | 孤儿 LV 检测 + 运维清理 | + +## 8.1 重试上限后的恢复策略 + +> **讨论结论**:采用“告警 + 下次 API 触发自动重试”的简化策略,移除 MetadataStaleEvent → recovery cycle 机制,避免无限重试循环。 + +当 `retryCount >= maxRetry`(默认 5 次,约 5 分钟)时: + +1. **告警**:ERROR 日志记录 vmUuid + 失败原因 + 重试次数 +2. **删除 dirty 行**:放弃本轮重试 +3. **自然恢复**:下次该 VM 的 `@MetadataImpact` API 成功 → `markDirty()` → 全新重试(retryCount=0) + +**为什么不需要 MetadataStaleEvent 恢复机制**: + +| 方面 | 旧方案(recovery cycle) | 新方案(告警 + API 重试) | +|------|--------------------------|---------------------------| +| 复杂度 | 需 ResourceConfig 持久化 cycle 计数 + 优先队列 + 定时任务 | 无额外代码 | +| 无限循环风险 | 需 cycle 上限 + permanently stale 标记 | 不存在(只在 API 触发时重试) | +| 恢复时机 | 固定延迟 5 分钟 | 自然发生(下次 API 时) | +| PS 持续故障 | cycle 耗尽后 permanently stale | 每次 API 都重试一轮(5 次退避),不会无限堆积 | + +> 健康巡检可作为远期兜底方案:发现不一致时调用 `markDirty()` 触发全新重试(本期暂不实现)。 + +## 8.2 ~~健康巡检~~(暂缓,不在本期实现) + +> **决策**:健康巡检功能暂时不做,后续版本根据实际运维需求再决定是否引入。 +> 当前恢复策略依赖"告警 + 下次 API 触发自动重试"机制(§8.1),以及级联删除 + FK CASCADE 自动清理。 + +## 8.3 VM 销毁时的元数据清理 + +在 `DestroyVmInstanceFlow` 链中增加 `NoRollbackFlow` step:查找根卷所在 PS → `metadataStorageHandler.deleteMetadata()` → **best-effort**,失败仅 WARN 日志,不阻塞 VM 销毁。 + +孤儿检测:健康巡检中匹配 `_vmmeta` LV 但无对应 `VmInstanceVO` → 标记孤儿 → 审计日志。 + +> dirty 行的清理由 FK CASCADE 自动完成(VM 销毁 → VmInstanceEO 删除 → dirty 行级联删除)。 + +--- + +# 9. 升级后全量刷新 + +## 9.1 触发条件 + +在 `managementNodeReady()` 回调中执行: + +1. 查询所有在线 `ManagementNodeVO`,收集 version 集合 +2. 若存在多个不同版本(滚动升级中)→ 跳过 +3. 版本唯一且与 `lastRefreshVersion`(GlobalConfig 持久化)不同 → 提交延迟 10 分钟的定时任务 +4. 10 分钟后再次检查所有 MN 版本是否一致 → 一致则执行全量刷新,不一致则跳过 + +> **延迟 10 分钟的原因**:滚动升级期间,第一个 MN 升级完成时可能短暂出现"版本唯一"假象(旧 MN 尚未恢复上线)。 + +## 9.2 刷新执行(简化,无 LongJob) + +> **讨论结论**:不需要 LongJob。直接批量 markDirty,Poller 自动处理。 + +```java +private void submitFullRefresh(String currentVersion) { + List vmUuids = getAllMetadataEnabledVmUuids(); + logger.info("metadata full refresh: {} VMs to refresh for version {}", + vmUuids.size(), currentVersion); + + // 批量 INSERT dirty 行 + for (String vmUuid : vmUuids) { + markDirty(vmUuid); // INSERT ON DUPLICATE KEY UPDATE + } + // Poller 自动分批处理,ChainTask 自动限流 + + // 更新 lastRefreshVersion + VmGlobalConfig.VM_METADATA_LAST_REFRESH_VERSION.updateValue(currentVersion); +} +``` + +> 如果 VM 数量很大(万级),逐个 `markDirty()` 的 INSERT 可优化为批量 SQL: +> ```sql +> INSERT INTO VmMetadataDirtyVO (vmInstanceUuid) +> SELECT uuid FROM VmInstanceVO WHERE ... +> ON DUPLICATE KEY UPDATE lastOpDate = CURRENT_TIMESTAMP +> ``` + +--- + +# 10. Payload 大小保护 + +在 `VmInstanceBase.doHandleUpdateVmInstanceMetadata()` 中,`buildVmInstanceMetadata()` 构建 payload 后进行大小检查: + +| 阈值 | 行为 | 说明 | +|------|------|------| +| > 8MB | WARN 日志 | 早期预警,提示运维关注 | +| > 30MB | ERROR + 拒绝写入 + reply 错误 | 保护 sblk LV 空间 | + +正常 VM 的 metadata payload 通常在 10KB~500KB 范围内。超过 8MB 几乎一定表示异常(如快照未清理导致数千条记录)。 + +--- + +# 11. 潜在代价与 tradeoff + +| 代价 | 说明 | 缓解 | +|------|------|------| +| Poller 空转 | 无 dirty 行时每 5s 执行一次 SELECT → 0 rows | 开销极小(一次空查询 <1ms),可接受 | +| 双 MN 负载不均 | 两个 Poller 竞争认领,不保证 50/50 | 最终一致性保证所有行都会被处理 | +| 新增一张 DB 表 | VmMetadataDirtyVO | 结构简单,维护成本低 | +| 退避期间 Poller 查到但跳过 | nextRetryTime 尚未到 → WHERE 条件排除 | 索引命中,开销可忽略 | + +--- + +# 12. 开发约束清单 + +## 12.1 API 标注约束 + +| # | 约束 | 原因 | 违反后果 | +|---|------|------|----------| +| A1 | 新增影响 VM 元数据的 API **必须**标注 `@MetadataImpact(Impact.CONFIG)` 或 `@MetadataImpact(Impact.STORAGE)` | 拦截器仅扫描带注解的 API 类 | 该 API 的变更不会触发元数据更新,存储侧数据过期 | +| A2 | 明确不影响元数据的 API **应当**标注 `@MetadataImpact(Impact.NONE)` | Opt-out 显式声明,利于 Code Review 审查覆盖率 | 无功能影响,但降低可审计性 | +| A3 | 涉及存储拓扑变更的 API(快照/迁移/删盘)必须使用 `Impact.STORAGE`,不可用 `Impact.CONFIG` | STORAGE 下发 OP type=2 通知 Agent 处理存储拓扑变更 | Agent 不执行存储拓扑处理,sblk 场景可能数据不一致 | +| A4 | `updateOnFailure=true` 仅用于可能部分成功的 API(如批量操作) | 默认 false:失败跳过;设为 true 时失败也 markDirty | 滥用会导致失败 API 也触发无意义的全量刷写 | + +## 12.2 VM UUID 解析约束 + +| # | 约束 | 原因 | 违反后果 | +|---|------|------|----------| +| B1 | 非 VM 直接 API(如 Volume/Nic/Tag API)必须有 `VmUuidFromApiResolver` 能够处理 | 默认 Resolver 链仅覆盖 `VmInstanceMessage`/`VolumeMessage`/Tag API + 反射兜底 | 相关 VM 不会被 markDirty,元数据不更新 | +| B2 | Resolver 的 `resolveVmUuids()` 必须在 **API 执行前**调用(`beforeDeliveryMessage` 阶段) | API 执行后资源可能已删除(如 APIDeleteVolumeMsg → VolumeVO 不存在) | 无法查到关联 VM,markDirty 丢失 | +| B3 | 新增资源类型关联 VM 时,需在 `ResourceBasedVmUuidFromApiResolver.resolveByResourceType()` 中补充映射 | 当前仅覆盖 VmInstanceVO/VolumeVO/VmNicVO/VolumeSnapshotVO | Tag 操作目标为新资源类型时不触发元数据更新 | + +## 12.3 元数据构建约束 + +| # | 约束 | 原因 | 违反后果 | +|---|------|------|----------| +| C1 | `buildVmInstanceMetadata()` 必须保留在 `VmMetadataBuilder` 中并标注 `@Transactional(readOnly=true)` | 6+ 条 SELECT 需在同一 REPEATABLE READ 快照内执行 | 读到跨快照不一致数据(如 Volume 存在但其 Snapshot 已被并发删除) | +| C2 | 新增元数据字段时,需同步更新 `VmInstanceMetadataDTO` 和 `VmMetadataBuilder` | DTO 是 payload 的唯一 schema 定义 | 字段不在 DTO 中则不会序列化到 payload | +| C3 | `ResourceMetadata` 中 `systemTags`/`resourceConfigs` 字段必须为 `String`(Base64 编码),不是 `List` | 编码管线:VO 列表 → JSON 序列化 → Base64 → 单 String | 类型不匹配导致序列化异常 | + +## 12.4 标脏与刷写约束 + +| # | 约束 | 原因 | 违反后果 | +|---|------|------|----------| +| D1 | 修改 VM 存储拓扑的**内部消息** handler 必须手动调用 `markDirty()` | 非 API 操作不经过 `VmMetadataUpdateInterceptor` | 变更后元数据不更新 | +| D2 | Handler 端写入失败时**不得**调用 `markDirty()`,必须 reply error 由上层重试 | Dirty 行已存在且由 Poller 管理 retryCount 和退避 | markDirty 重置 retryCount,绕过退避机制,可能无限快速重试 | +| D3 | Agent 端写入必须幂等(全量覆盖,不做增量 merge) | 同一 VM 的并发刷写(跨 MN 极端场景)最终应收敛到一致状态 | 增量 merge 可能导致数据残留或顺序依赖 | +| D4 | `exceedMaxPendingCallback` 中**不得**释放认领(`releaseClaim`) | 该行可能正在被同 MN 的 running task 处理,释放后其他 MN 可能并发认领 | 跨 MN 并发刷写同一 VM | + +## 12.5 并发与线程约束 + +| # | 约束 | 原因 | 违反后果 | +|---|------|------|----------| +| E1 | `nodeLeft()`/`nodeJoined()` 回调中的 DB 操作必须通过 `thdf.submit()` 异步执行 | 回调在心跳检测线程上,阻塞会影响其他 MN 状态检测 | 心跳超时导致误判 MN 离线 | +| E2 | per-VM ChainTask(`metadata-dirty-flush-vm-{uuid}`)的 `maxPending=1`,不得修改 | 确保同一 VM 最多排队 1 个 pending 任务(+ 1 running) | pending 过多导致重复提交堆积 | +| E3 | 外层全局队列 `syncLevel` 和 Layer 3 per-PS 队列 `syncLevel` 的调整需评估 DB 连接池和 Agent 并发承受力 | 二者嵌套:全局水位 × per-PS 水位 决定实际并发 | 过大导致 DB/Agent 过载,过小导致刷写积压 | + +--- + +# 13. GlobalConfig 配置项汇总 + +| 配置项 | 类型 | 默认值 | 说明 | 章节 | +|--------|------|--------|------|------| +| `vm.metadata.enabled` | Boolean | false | 元数据功能总开关 | §1 | +| `vm.metadata.dirty.pollIntervalSec` | Long | 5 | Poller 轮询间隔(秒),可动态调整 | §4.1 | +| `vm.metadata.dirty.batchSize` | Integer | 50 | 每轮 Poller 最多认领行数 | §4.2 | +| `vm.metadata.maxRetry` | Integer | 5 | 最大重试次数(达上限后告警 + 删除,下次 API 自动重试) | §4.6 | +| `vm.metadata.ps.maxConcurrent` | Integer | 5 | 同一 MN 同一 PS 最大并发写入 | §6.1 | +| `vm.metadata.global.maxConcurrent` | Integer | 10 | 同一 MN 最大并发 VM 更新数 | §6.2 | diff --git a/docs/design/vm-metadata-03-registration.md b/docs/design/vm-metadata-03-registration.md new file mode 100644 index 00000000000..177c104f8fc --- /dev/null +++ b/docs/design/vm-metadata-03-registration.md @@ -0,0 +1,555 @@ +# 注册与运维 + +| 属性 | 值 | +|------|-----| +| 文档版本 | 2.0 | +| 最后更新 | 2026-03-03 | +| 状态 | 设计中 | + +**修订记录** + +| 版本 | 日期 | 变更说明 | +|------|------|----------| +| 1.0 | 2026-02 | 初始版本 | +| 2.0 | 2026-03-03 | 重构:跨存储规则改为拒绝注册、schemaVersion 支持强制注册参数、按 PRD 格式重排 | + +--- + +## 目录 + +1. [注册字段处理矩阵](#1-注册字段处理矩阵) +2. [跨存储数据盘处理规则](#2-跨存储数据盘处理规则) +3. [注册虚拟机详细流程](#3-注册虚拟机详细流程) +4. [注册事务回滚](#4-注册事务回滚) +5. [注册场景问题分析](#5-注册场景问题分析) +6. [扫描虚拟机](#6-扫描虚拟机) +7. [可观测性](#7-可观测性) +8. [设计决策汇总](#8-设计决策汇总) +9. [异常处理 — 错误码定义](#9-异常处理--错误码定义) + +--- + +## 1. 注册字段处理矩阵 + +### 1.1 VmInstanceVO + +| 字段 | 处理方式 | 说明 | +|------|----------|------| +| uuid | 保留 | 冲突时拒绝注册 | +| name | 保留 | — | +| description | 保留 | — | +| zoneUuid | API 参数 | 必填 | +| clusterUuid | API 参数 | 必填,赋值到 VO,决定首次启动调度范围 | +| hostUuid | 设 null | 注册后 VM 为 Stopped 状态 | +| lastHostUuid | 设 null | 新环境无意义 | +| instanceOfferingUuid | 设 null | 新环境可能不存在 | +| imageUuid | 保留 | 不校验存在性,仅在服务端日志记录。若指向不存在的镜像,`ReimageVmInstance`/`CloneVmInstance` 等操作会自行校验失败 | +| cpuNum | 保留 | — | +| memorySize | 保留 | — | +| platform | 保留 | — | +| architecture | 保留 | — | +| hypervisorType | 保留 | — | +| state | 硬编码 | Registering → Stopped | +| defaultL3NetworkUuid | 设 null | 网络不恢复 | +| managementNetworkUuid | 设 null | 网络不恢复 | +| accountUuid | 替换 | 当前调用者(admin) | + +### 1.2 VolumeVO + +| 字段 | 处理方式 | 说明 | +|------|----------|------| +| uuid | 保留 | 冲突时拒绝注册 | +| primaryStorageUuid | 替换 | 新主存储 UUID | +| installPath | 替换 | 路径映射(vg uuid / 挂载路径替换) | +| diskOfferingUuid | 设 null | 新环境可能不存在 | +| vmInstanceUuid | 保留 | 与注册 VM UUID 一致 | +| accountUuid | 替换 | 当前调用者 | + +### 1.3 VolumeSnapshotVO + +| 字段 | 处理方式 | 说明 | +|------|----------|------| +| uuid | 保留 | 冲突时拒绝注册 | +| primaryStorageUuid | 替换 | 新主存储 UUID | +| primaryStorageInstallPath | 替换 | 路径映射 | +| volumeUuid | 保留 | — | +| parentUuid | 保留 | 快照链关系 | + +### 1.4 SystemTagVO / ResourceConfigVO + +| 字段 | 处理方式 | 说明 | +|------|----------|------| +| id | 自增 | 数据库自动生成 | +| uuid | 重新生成 | `Platform.getUuid()` | +| resourceUuid | 保留 | 指向 VM/Volume 的 UUID 不变 | +| 其余字段 | 保留 | — | + +### 1.5 TemplatedVmInstanceVO(模板虚拟机标记) + +当元数据中 `isTemplated = true` 时,注册流程需恢复模板 VM 身份。 + +| 字段 | 处理方式 | 说明 | +|------|----------|------| +| uuid | 保留 | 与 VmInstanceVO.uuid 一致(FK CASCADE) | +| createDate | 重新生成 | 以注册时间为准 | +| lastOpDate | 重新生成 | 以注册时间为准 | + +**不恢复的关联表**: + +| VO | 处理方式 | 理由 | +|----|----------|------| +| `TemplatedVmInstanceCacheVO` | 不恢复 | 缓存 VM 是运行态产物,新环境首次从模板创建 VM 时自动创建 | +| `TemplatedVmInstanceRefVO` | 不恢复 | 子 VM 追溯关系属于旧环境,新环境不存在对应子 VM | + +--- + +## 2. 跨存储数据盘处理规则 + +### 2.1 策略 + +虚拟机的所有磁盘(根盘 + 所有数据盘)必须位于同一主存储,否则**拒绝注册**。 + +**原因**: + +- 跨存储时路径映射规则不统一(sblk 用 vg uuid 替换、NFS/Local 用挂载路径替换),混合存储的 installPath 无法在一次注册中完成。 +- 快照组(SnapshotGroup)跨存储时会出现不完整引用,恢复语义复杂且易出错。 +- 单存储注册简化了变基(rebase)、回滚、幂等等所有流程。 + +### 2.2 校验实现 + +在注册前置校验阶段,从元数据中提取所有 Volume 的 `primaryStorageUuid`(原值),检查经路径映射后是否全部归属到同一目标主存储。如果存在归属到不同主存储的 Volume,返回 `METADATA_CROSS_STORAGE` 错误并列出不匹配的 Volume UUID 和对应的存储信息。 + +### 2.3 SnapshotGroup 处理 + +由于所有磁盘在同一存储上,SnapshotGroup 天然完整——所有 ref 对应的 Volume 均在本次注册范围内。无需处理 incomplete 标记、分步注册、创建或复用等复杂逻辑。 + +SnapshotGroupVO 和 SnapshotGroupRefVO 在同一事务内一次性创建。 + +--- + +## 3. 注册虚拟机详细流程 + +### 3.1 API 定义 + +- **仅 admin 账户**可使用注册 API +- 注册结果返回 `warnings` 列表(如 schemaVersion 不匹配提醒、模板 VM 缓存提醒等) +- API 参数 `force`(Boolean,默认 false):为 true 时 schemaVersion 不匹配也继续注册(见 §3.4 步骤 1) + +### 3.2 状态流 + +``` +(new) → Registering → Stopped → Starting → Running + │ + └── 失败 → 回滚删除所有 VO +``` + +### 3.3 "注册 VM 未首次启动" ResourceConfig + +| 时机 | 操作 | +|------|------| +| 注册完成 | 创建 `vm.metadata.registered.not.started` ResourceConfig | +| VM 首次到达 Running 状态 | 删除该 ResourceConfig,立即触发 `markDirty` 更新元数据 | +| 存在该 ResourceConfig 时 | 任何 `@MetadataImpact` API 的元数据更新被跳过 | + +### 3.4 完整注册步骤 + +``` +1. 前置校验 + ├── 元数据 JSON 解析 + systemTags/resourceConfigs Base64 解码 + Validator 校验 + ├── schemaVersion 匹配检查 + │ ├── 匹配 → 继续 + │ ├── 不匹配 + force=false → 拒绝注册(METADATA_VERSION_MISMATCH) + │ └── 不匹配 + force=true → 继续注册,VO 中缺失的字段置为 null, + │ warnings 中记录 "schemaVersion mismatch, missing fields set to null" + ├── 跨存储校验:所有 Volume 必须归属同一目标主存储(见 §2) + ├── UUID 冲突检测(VM/Volume/Snapshot/SnapshotGroup/SnapshotGroupRef/Reference/ReferenceTree) + │ ├── 冲突且是 Registering 遗留 → 幂等回滚后重新注册 + │ └── 冲突且是正常资源 → 拒绝 + └── installPath 替换 + 路径存在性检查(Agent 校验) + +2. 创建 VmInstanceVO + ├── state = Registering + ├── 打 SystemTag: vmMetadata::registeringMnUuid::{mnUuid} + ├── 打 SystemTag: vmMetadata::registeringStartTime::{timestamp} + └── 创建 "注册VM未首次启动" ResourceConfig + +3. 还原 SystemTag / ResourceConfig + ├── 为 SystemTag 和 ResourceConfig 生成新 UUID(Platform.getUuid()) + └── resourceUuid 保持与 VM/Volume UUID 一致 + +4. 创建 VolumeVO + ├── 替换 primaryStorageUuid、installPath、accountUuid + └── 还原 volume 级 SystemTag / ResourceConfig + +5. 快照还原 + ├── 获取元数据中所有快照,按所属快照树归类 + ├── 每棵快照树使用 VolumeSnapshotTree.fromInventories() 构建 + │ ├── 创建 VolumeSnapshotTreeVO + │ ├── 层级遍历快照树,按顺序创建 VolumeSnapshotVO + │ └── 校验每个 parentUuid 在已创建集合中存在(防御性校验) + ├── 创建 VolumeSnapshotGroupVO + VolumeSnapshotGroupRefVO + ├── 创建 VolumeSnapshotReferenceVO + VolumeSnapshotReferenceTreeVO + └── 事务策略:上述所有 persist 在单个 @Transactional 内完成 + (批量 persist 每 100 条 flush + clear,异常触发回滚 → Saga 补偿链) + +6. 执行变基(sblk / local / NFS) + ├── 幂等:先 qemu-img info 检查当前 backing file + │ ├── 已指向目标路径 → 跳过 + │ ├── 指向旧路径 → 执行 qemu-img rebase -u + │ └── 指向异常路径 → 报错 + ├── 变基失败 → 整个注册回滚(删除所有已创建 VO) + └── 变基成功 → 继续 + +7. 恢复模板 VM 身份(仅当 isTemplated = true) + ├── 创建 TemplatedVmInstanceVO(uuid = VM UUID) + ├── 创建 VmHaVO(haLevel=NeverStop,禁止模板 VM 触发 HA) + └── 强制 state = Stopped(模板 VM 禁止直接启动) + +8. 注册成功 + ├── 更新 VmInstanceVO.state = Stopped + ├── 删除 registeringMnUuid tag + └── 返回结果(含 warnings) +``` + +### 3.5 sblk 变基详细流程 + +``` +原存储: sblkA (vg_uuid = "123xxx") +新存储: sblkB (vg_uuid = "456xxx") + +快照链: + /dev/123xxx/lv1_uuid + /dev/123xxx/lv2_uuid (backing = /dev/123xxx/lv1_uuid) + /dev/123xxx/lv3_uuid (backing = /dev/123xxx/lv2_uuid) + +注册步骤: + 1. 读取元数据 /dev/456xxx/vm1_uuid_vmmeta + 2. 替换 VO 中 vg uuid: 123xxx → 456xxx(前缀锚定替换) + 3. 构建 installPath 映射(旧路径 → 新路径) + 4. 检查替换后的 installPath 在存储上存在 + 5. 创建所有 VO + 6. 变基: 对每个有 backing file 的 LV 执行 + qemu-img rebase -u -b <新backing路径> <当前LV路径> +``` + +**路径替换安全机制**:使用前缀锚定替换(`String.startsWith(oldPrefix)` 检查后字符串拼接),而非 `replaceFirst()`。路径格式需通过正则预校验(如 sblk 格式 `/dev/{32位hex}/{32位hex}_\w+`)。 + +### 3.6 local / NFS 变基详细流程 + +``` +原环境: /vms_ds/rootVolumes/acct-user1/vol-xxx/volume1.qcow2 +新环境: /vms_ds2/rootVolumes/acct-user1/vol-xxx/volume1.qcow2 + +注册步骤: + 1. 读取元数据 + 2. 替换 installPath 中存储挂载路径: /vms_ds/ → /vms_ds2/ + 3. 构建 installPath 映射 + 4. 注册时不移动文件(账户替换仅在 DB 层面) + 5. 变基: qemu-img rebase -u 修改 backing file 路径 +``` + +> `qemu-img rebase -u` 只修改文件 backing file 元数据,不合并数据,操作极快。 + +### 3.7 无网卡 VM 的启动行为 + +注册后 VM 无网卡是允许的状态: + +| 组件 | 行为 | +|------|------| +| `VmAllocateNicFlow` | nics 为空时跳过网卡分配 | +| `StartVmInstance` | `defaultL3NetworkUuid == null` 时不报错,允许无网卡启动 | +| 推荐流程 | 先给 VM 加网卡(`AttachVmNicToVm`),再启动 | + +**保留网卡元数据的意义**:后续可能实现网络映射功能,会用到原网卡信息(如原 L3 网络、IP 地址等用于自动映射到新环境网络)。 + +### 3.8 Registering 状态 VM 的可见性 + +- `Registering` 状态的 VM **仅 admin 可见** +- 普通用户 `QueryVmInstance` 自动过滤 `state=Registering` +- admin 用户可查询到,但变更操作被拦截器拒绝(仅允许查询和取消注册) + +--- + +## 4. 注册事务回滚 + +### 4.1 注册期间出错 + +以 VM UUID 为锚点,按以下顺序删除当前注册创建的所有 VO: + +1. VolumeSnapshotReferenceTreeVO / VolumeSnapshotReferenceVO +2. VolumeSnapshotGroupRefVO / VolumeSnapshotGroupVO +3. VolumeSnapshotVO +4. VolumeVO(含 SystemTag / ResourceConfig) +5. TemplatedVmInstanceVO(如有) +6. VmHaVO 模板抑制记录(如有) +7. VmInstanceVO(含 SystemTag / ResourceConfig) + +**不操作存储**:存储上的数据是用户迁移的,不因注册失败而删除。 + +### 4.2 MN 崩溃导致注册中断 + +MN 启动时扫描 `state=Registering` 的 VM,需满足以下 5 个条件才执行回滚: + +| # | 条件 | 说明 | +|---|------|------| +| 1 | `VM.state == Registering` | 处于注册过渡态 | +| 2 | `VM.managementNodeUuid ∈ deadMNs \|\| IS NULL` | 关联 MN 已离线 | +| 3 | `now() - VM.lastOpDate > 30min` | 最后进度心跳超过 30 分钟前,注册流程已卡死(正常流程每步骤更新 lastOpDate,变基期间每 30 秒心跳) | +| 4 | 当前 MN 无此 VM 的活跃 ChainTask | 无人在处理 | +| 5 | CAS: `UPDATE ... SET state='Destroying' WHERE state='Registering' AND mnUuid=?` 成功 | 防并发 | + +**注册流程心跳**:注册流程每步骤完成后更新 `lastOpDate` 作为进度心跳。变基步骤(可能持续数分钟)内部启动定时器每 30 秒更新一次 `lastOpDate`,完成后取消。所有心跳操作 best-effort,失败只记 warn 日志。 + +**触发时机**:MN 启动 / `ManagementNodeLeftEvent` / 每 5 分钟定时。 + +**MN UUID 判断规则**: + +| 条件 | 行为 | 说明 | +|------|------|------| +| tag 中 mnUuid = 当前 MN UUID | 回滚 | 本 MN 上次注册中途中断 | +| tag 中 mnUuid ≠ 当前 MN UUID,且该 MN 不在线 | 回滚 | 原 MN 已崩溃,安全清理 | +| tag 中 mnUuid ≠ 当前 MN UUID,且该 MN 在线 | 跳过 | 另一个 MN 可能正在注册 | +| tag 中 mnUuid 与所有在线 MN UUID 都不同 | 回滚 | 原 MN 已不存在(UUID 变化场景) | +| 超过 `MAX_REGISTERING_TIMEOUT`(30 分钟) | CAS 后强制回滚 | 超时兜底 | + +**强制回滚前再次检查**:再次读取 `VmInstanceVO.state` 确认仍为 Registering → CAS 更新 tag `registeringMnUuid → rollingBackMnUuid::{currentMnUuid}` → CAS 成功才执行回滚。 + +### 4.3 多 MN 并发回滚 + +集群重启时多个 MN 同时检测到同一 Registering VM。使用 SystemTag CAS 保证只有一个 MN 执行:`UPDATE ... WHERE tag = oldValue` 实现 CAS,只有一个 MN 更新成功。不引入新的 `VmInstanceState` 枚举,不影响现有状态机。 + +### 4.4 回滚范围确定 + +采用"标记 + 全量清理"策略:注册第一步创建 VmInstanceVO(Registering 状态)并打上 `registeringMnUuid` tag → 所有后续写入资源关联到该 VM UUID → 回滚时以 VM UUID 为锚点删除所有关联资源 → 最后删除 VmInstanceVO。 + +### 4.5 回滚本身失败 + +回滚操作设计为**可重入/幂等**:每次注册前检查目标 VM UUID 是否 Registering 状态 → 是则无条件执行回滚 → `DELETE` 操作天然幂等。DB 持续不可用属于系统级故障,不单独处理。 + +### 4.6 存储数据不删除 + +注册回滚只清理 DB 记录,不操作存储。存储上的数据是用户迁移的,不应因注册失败而删除。 + +--- + +## 5. 注册场景问题分析 + +### 5.1 UUID 冲突 + +**触发场景**:同一份存储被多次注册(误操作)/ 上次注册部分成功后重试 / 跨环境 UUID 碰撞(概率极低)。 + +**处理方案**:注册前批量查询所有涉及的 UUID,任一冲突立即拒绝并返回冲突明细。 + +**幂等注册**:检测到 UUID 冲突时判断冲突资源是否为上次注册遗留(检查 VM 是否带有 `vmMetadata::registeringMnUuid::` SystemTag)→ 是遗留(Registering 状态)→ 回滚清理后重新注册 → 资源是正常状态(非 Registering)→ 拒绝。 + +### 5.2 installPath 映射 + +API 要求用户提供 `oldPathIdentifier` 和 `newPathIdentifier`,使用前缀锚定替换。 + +**替换后校验**:校验所有 installPath 包含 `newPathIdentifier` → 不包含则报错拒绝 → 向 Agent 发送检查命令验证路径实际存在。 + +**Agent 不可达**:注册 API 本身要求主存储在线(需要读取元数据),如果此时存储/Agent 不可达,API 在更早阶段已失败。路径检查不引入额外失败点。 + +**文件不移动**:注册流程中不移动文件。账户替换只在 DB 层面(`VolumeVO.accountUuid → admin`),文件物理位置不变。移动大文件有失败风险,且跨文件系统移动非原子操作。 + +### 5.3 元数据损坏/不完整 + +JSON 解析 / Base64 字段解码 / 校验器任一步骤失败 → 拒绝注册。 + +**sblk 双 Slot 容错**:Active Slot Checksum 失败 → 切换 Backup Slot → Backup Slot 也失败 → 拒绝注册。Backup Slot 是上一版本数据 → 可能缺少最新快照 → 注册后建议执行"存储一致性扫描"。 + +**local/NFS 写入完整性**:采用 tmp + fsync + rename 原子写入。NFS v3/v4 同目录 rename 是原子的(RFC 7530)。 + +### 5.4 快照链变基的幂等性 + +执行 `qemu-img rebase -u` 前先执行 `qemu-img info` 检查当前 backing file: + +| 当前 backing file | 行为 | 说明 | +|-------------------|------|------| +| 已指向目标路径 | 跳过 | 上次执行已成功 | +| 指向旧路径 | 执行变基 | 正常流程 | +| 指向其他路径 | 报错 | 异常状态,需人工介入 | + +### 5.5 部分快照树失败 + +快照还原原子性以 **VM 为粒度**:任一快照树创建失败 → 整个注册回滚(删除所有已创建 VO),不做部分成功。部分成功会导致快照组引用不完整、快照链关系混乱,不如整体重试。 + +### 5.6 并发操作 + +- Registering 状态 VM 只允许查询,变更操作被拦截器拒绝 +- ChainTask 串行:`syncSignature = vm-register-{vmUuid}` +- DB 主键约束防 UUID 并发创建 +- 并发 `dbf.persist()` 触发 `DuplicateKeyException` → 触发回滚 +- 回滚只删 `vmInstanceUuid = thisVmUuid` 的资源,不会删并发写入的其他资源 + +### 5.7 大量快照导致元数据过大 + +24 盘 × 256 快照,元数据可达 10MB+。消息中直接传输 `encodedMetadata` 字符串(CloudBusImpl3 基于 HTTP 传输,无硬性大小限制)。单次更新峰值约 40MB(DTO + JSON + 消息序列化,主体数据无 Base64 膨胀),对 4–8GB JVM 可接受。ChainTask `maxPendingTasks=1` 天然限制同一 VM 并发。 + +### 5.8 注册后 VM 启动失败 + +注册只保证 DB 一致性,不保证 VM 可启动。API 返回 `warnings` 列表提示潜在问题。 + +### 5.9 同一 VM 在多个存储上都有元数据 + +- 用户应使用包含实际磁盘数据的存储上的元数据注册 +- 使用错误存储的元数据 → installPath 文件存在性检查失败 +- 存储迁移成功后触发新存储元数据更新 +- 异步清理旧存储上的元数据(清理前 double-check 确认 VM 根盘确实不在旧存储上) + +### 5.10 模板虚拟机注册 + +当元数据 `isTemplated = true` 时,注册流程需额外处理模板 VM 身份恢复。 + +#### 5.10.1 注册流程差异 + +| 项目 | 普通 VM | 模板 VM | +|------|---------|----------| +| TemplatedVmInstanceVO | 不创建 | 创建(uuid = VM UUID) | +| VmHaVO 抑制 | 不处理 | 创建 VmHaVO(haLevel=NeverStop) | +| CacheVO | 不涉及 | 不恢复(运行态产物) | +| RefVO | 不涉及 | 不恢复(旧环境关系) | +| 状态 | Stopped | 强制 Stopped(模板 VM 不允许直接启动) | + +#### 5.10.2 HA 抑制 + +模板 VM 在生产环境中通过 `VmHaVO`(`haLevel = NeverStop`)确保模板 VM 不会被 HA 服务自动启动。注册时创建此记录,NeverStop 级别使模板 VM 保持永远 Stopped 状态。用户将模板 VM 转换回普通 VM 后,手动删除 VmHaVO 记录。 + +#### 5.10.3 CacheVO 不恢复的影响 + +| 操作 | 行为 | +|------|------| +| 首次从模板创建 VM | 自动触发 Cache VM 创建(Clone + 快照组),耗时较长 | +| 后续从模板创建 VM | Cache VM 已存在,速度恢复正常 | +| warnings | 提示用户:"模板 VM 已注册,首次创建子 VM 时将自动创建缓存,耗时可能较长" | + +#### 5.10.4 RefVO 不恢复的影响 + +`TemplatedVmInstanceRefVO` 记录从模板创建的子 VM UUID。旧环境的子 VM 在新环境不存在,恢复 RefVO 会产生悬挂引用。新环境从模板创建新子 VM 时会自动生成新的 RefVO。 + +#### 5.10.5 注册回滚 + +模板 VM 注册回滚时,除普通回滚步骤外,额外删除 `TemplatedVmInstanceVO` 和 `VmHaVO`(模板 VM 的 HA 抑制记录)。 + +--- + +## 6. 扫描虚拟机 + +`APIGetVmInstanceMetadataFromPrimaryStorageMsg`(仅 admin,同步 GET):获取指定主存储上所有虚拟机元数据文件的概要信息。 + +> **API 详细定义**见 [Part 1 §12.1](vm-metadata-01-design.md#121-获取主存储上的虚拟机元数据列表)。 + +**API 参数**:`uuid`(主存储 UUID,必填)。一次性返回所有结果,不分页。 + +| 存储类型 | 扫描方式 | +|----------|----------| +| sblk | 遍历 VG 中所有 LV,筛选 `lv_name.endswith('_vmmeta')` | +| local/NFS | 遍历根盘目录 `{存储挂载路径}/rootVolumes/acct-xxx/vol-xxx/`,筛选 `*_vmmeta` 文件 | + +**返回值**:`List`,每项包含 `name`(VM 名称)、`uuid`(VM UUID)、`path`(元数据文件路径)。 + +> **与原 `APIScanVmMetadataOnPrimaryStorageMsg` 的关系**:原设计提供分页参数(`start`/`limit`),经评估后简化为一次性返回——元数据文件数量与 VM 数量相当,通常不超过数千个,分页增加了客户端复杂度但无显著收益。API 名称统一为 `APIGetVmInstanceMetadataFromPrimaryStorageMsg`。 + +--- + +## 7. 可观测性 + +### 7.1 运维告警 + +新增报警器:**更新虚拟机元数据失败**。触发条件:达到最大重试次数仍失败。告警内容包含 vmUuid 和 psUuid。告警级别 WARNING。 + +### 7.2 一致性检查 API + +`APICheckVmInstanceMetadataConsistencyMsg`(仅 CLI 运维使用,UI 不展示):从数据库构建一份元数据 → 从存储读取虚拟机元数据 → 结构化比较 → 一致返回 `consistent: true`,不一致返回 DB 侧和存储侧的完整 JSON 供外部 diff。 + +> **API 详细定义**见 [Part 1 §12.4](vm-metadata-01-design.md#124-检查虚拟机元数据一致性)。 + +**比较时排除的字段**:`lastOpDate`(时间戳差异不影响业务语义)、`id`(自增 ID 不属于业务数据)、`managementNodeUuid`(运行时状态)。其余 VO 字段逐字段比较,任何差异都记入 diff 结果。 + +### 7.3 注册预检查 API + +`APIPreCheckVmMetadataRegistrationMsg`(仅 CLI 运维使用)。API 详细定义见 [Part 1 §12.5.2](vm-metadata-01-design.md#1252-注册预检查)。 + +检查内容: + +| # | 检查项 | 条件 | 失败错误码 | 级别 | +|---|--------|------|-----------|------| +| 1 | VM UUID 唯一性 | `!exists(VmInstanceVO, uuid)` | `REG.UUID_CONFLICT` | ERROR | +| 2 | Volume UUID 唯一性 | `!exists(VolumeVO, uuid) for all` | `REG.UUID_CONFLICT` | ERROR | +| 3 | Snapshot UUID 唯一性 | `!exists(VolumeSnapshotVO, uuid) for all` | `REG.UUID_CONFLICT` | ERROR | +| 4 | PS 可达性 | `PS.status == Connected` | `REG.PS_NOT_AVAILABLE` | ERROR | +| 5 | PS 类型兼容 | `type ∈ {SharedBlock, LocalStorage, NFS}` | `REG.PS_INCOMPATIBLE` | ERROR | +| 6 | 跨存储校验 | 所有 Volume 归属同一目标 PS | `REG.CROSS_STORAGE` | ERROR | +| 7 | 存储路径可映射 | 前缀锚定替换后 Agent 验证路径存在 | `REG.PATH_UNMAPPABLE` | ERROR | +| 8 | Schema 版本兼容 | `≤ MAX_SUPPORTED_SCHEMA_VERSION` | `REG.SCHEMA_INCOMPATIBLE` | ERROR | +| 9 | Host 资源充足 | 至少一台 Host 满足 cpu/memory(考虑 overcommit ratio) | `REG.NO_CAPABLE_HOST` | WARNING | + +辅助检查项: + +| 检查项 | 检查方式 | 级别 | +|--------|----------|------| +| 磁盘文件可读性 | Agent `qemu-img info` | WARNING | +| architecture 兼容性 | 检查目标 Zone 集群 | WARNING | +| hypervisorType 兼容性 | 检查目标 Zone hypervisor | WARNING | +| 元数据完整性 | Validator 全量校验 | ERROR | +| imageUuid 存在性 | 查询 ImageVO | WARNING | + +> 预检查是"尽力而为"的辅助工具,不作为注册前置依赖。注册流程内部有自己的校验。 + +### 7.4 手动触发元数据更新 API + +`APIUpdateVmMetadataMsg`(仅 admin CLI 使用,UI 不开放):指定 vmUuid,手动触发一次全量元数据更新。用于达到最大重试次数后的手动恢复,或升级后单独更新指定 VM 的元数据。 + +> **API 详细定义**见 [Part 1 §12.5.1](vm-metadata-01-design.md#1251-手动触发元数据更新)。 + +--- + +## 8. 设计决策汇总 + +| 问题域 | 决策 | 理由 | +|--------|------|------| +| UUID 冲突 | 前置全量检查 + Registering 状态幂等回滚 | 防重复注册,支持安全重试 | +| MN 崩溃 | SystemTag 标记 + 启动扫描 + 超时兜底 + CAS 防并发 | 防中间状态泄漏 | +| 版本不匹配 | 默认拒绝 + `force=true` 允许强制注册(缺失字段置 null) | 兼顾安全性和灾难恢复灵活性 | +| 路径映射 | 用户提供标识符 + 前缀锚定替换 + 替换后校验 + 文件存在性检查 | 简单可靠 | +| 跨存储 | 拒绝注册,要求所有磁盘在同一存储 | 消除分步注册/incomplete 等复杂性 | +| 并发控制 | ChainTask 串行 + DB 主键约束 + CAS | 多层防护 | +| 数据损坏 | sblk 双 Slot 容错 + local/NFS 原子写入 + 解码校验 | 多级容错 | +| 大数据量 | 消息直传 + 全局并发控制 | 简化传输,限制资源 | +| 启动失败 | 注册只保证 DB 一致 + 预检查 API | 职责分离 | +| 旧元数据 | 异步清理 + 文件存在性校验 | 异步安全清理 | +| schemaVersion | 数据库版本号(`dbf.getDbVersion()`),`force=true` 允许跨版本注册 | 与数据库 schema 完全一致 | +| SystemTag 过滤 | 白名单注册 + CI 检查 | 新增 tag 自动被发现 | +| VO JSON 范围 | 所有非 @Transient 字段 | id 重生成,createDate 保留 | +| 压缩策略 | 不压缩 | 正常场景 <100KB,简化调试 | +| @MetadataImpact | 显式标注 + CI 强制 | 避免遗漏或误拦截 | +| Resolver 时机 | API 前预捕获 + API 后提交 | 解决 Detach 类操作问题 | +| 变基幂等 | `qemu-img info` 预检查 | 支持安全重试 | +| 文件移动 | 不移动,仅 DB 层面替换 accountUuid | 避免大文件移动风险 | +| 回滚条件 | tag mnUuid + 在线检查 + 超时兜底 + CAS | 防误删,防并发 | +| 不支持的存储 | 静默跳过 | 不影响非容灾用户 | +| Registering 可见性 | 仅 admin 可见 | 避免普通用户困惑 | +| 模板 VM isTemplated | boolean 字段代替完整 VO | TemplatedVmInstanceVO 无业务字段 | +| CacheVO | 不恢复 | 运行态产物,新环境自动创建 | +| RefVO | 不恢复 | 子 VM 追溯属于旧环境,新环境自动生成 | +| 模板 VM HA 抑制 | 注册时创建 VmHaVO(haLevel=NeverStop) | 防止模板 VM 被 HA 自动启动 | +| 模板 VM 状态 | 强制 Stopped | 模板 VM 禁止直接启动 | +| 存储数据 | 注册回滚不删除存储数据 | 存储数据由用户迁移,不因注册失败删除 | + +--- + +## 9. 异常处理 — 错误码定义 + +| 错误码 | 含义 | 触发场景 | +|--------|------|----------| +| `METADATA_FILE_NOT_FOUND` | 元数据文件不存在 | 读取/注册时文件不存在 | +| `METADATA_CHECKSUM_MISMATCH` | SHA256 校验失败 | sblk Slot 数据损坏 | +| `METADATA_VERSION_MISMATCH` | schemaVersion 不匹配 | 跨版本注册且 force=false | +| `METADATA_UUID_CONFLICT` | UUID 与现有资源冲突 | 重复注册 | +| `METADATA_CROSS_STORAGE` | 磁盘分布在不同主存储 | 跨存储注册 | +| `METADATA_STORAGE_UNREACHABLE` | 存储路径不可达 | Agent 不可用或路径不存在 | +| `METADATA_REBASE_FAILED` | 快照链变基失败 | `qemu-img rebase` 执行失败 | +| `METADATA_LV_SPACE_INSUFFICIENT` | LV 空间不足 | payload 超过 64MB 上限 | +| `METADATA_STORAGE_NOT_SUPPORTED` | 存储类型不支持 | ceph/zbs/vhost 注册 | +| `METADATA_BASE64_DECODE_FAILED` | Base64 解码失败 | systemTags/resourceConfigs 字段 Base64 解码失败 | +| `METADATA_PARENT_UUID_DANGLING` | 快照 parentUuid 悬挂 | 快照链引用完整性校验失败 | diff --git a/docs/design/vm-metadata-04-sblk.md b/docs/design/vm-metadata-04-sblk.md new file mode 100644 index 00000000000..bafbaca2cfc --- /dev/null +++ b/docs/design/vm-metadata-04-sblk.md @@ -0,0 +1,1338 @@ +# sblk 二进制存储协议 + +## 1. 背景 + +ZStack 共享块存储(sblk)场景下,VM 元数据需要持久化到 LVM Logical Volume 上。多个管理节点可能通过共享块设备并发访问同一 LV。 + +核心挑战: + +- **无文件系统**:LV 是裸块设备,无法使用常规文件 I/O +- **共享访问**:多节点通过 O_DIRECT 绕过 page cache 直接读写 +- **崩溃安全**:任意时刻断电或进程崩溃后,数据必须可恢复 +- **空间受限**:LV 初始 4MB,最大 64MB,需高效利用 + +> **对比 local/NFS**:文件系统场景使用 JSON 明文 + tmp + fsync + rename 原子写, +> sblk 因裸块设备特性需要完全不同的存储协议。 + +### 1.1 灾备接管场景 — AB 双 Slot 的核心驱动力 + +除常规读写外,sblk 元数据协议必须支持**跨平台灾备接管**场景: + +``` +环境: + sanA / sanB — 两套拥有相同 LUN(数量和大小)的 SAN 存储 + zsvA(原平台)/ zsvB(目标平台)— 两套独立的 ZSV 管理平台 + +操作流程: + 1. zsvA 将 sanA 添加为 sblk 存储,在上面创建 VM 并正常读写 + 2. zsvB 将 sanB 添加为存储目标(iSCSI server),但不注册为 sblk 存储 + 3. 存储侧配置 sanA → sanB 的 LUN 级数据复制(块级,平台不感知) + 4. zsvA 的 sanA 发生故障 + 5. zsvB 使用 sanB 注册 sblk 存储,通过扫描 LV 上的元数据恢复 VM +``` + +此场景下 LV 元数据的角色发生本质转变: + +| | 正常运行 | 灾备接管 | +|---|---------|---------| +| **元数据权威来源** | 管理面 DB | **LV 上的元数据** | +| **LV 元数据角色** | DB 的副本/缓存 | **唯一的 VM 恢复来源** | +| **管理面 DB 可用?** | ✅ zsvA DB 可用 | ❌ zsvA 故障,zsvB DB 无此 VM 记录 | +| **full-refresh 可行?** | ✅ 从 DB 重建 | ❌ **无 DB 数据可重建** | + +**核心问题**:存储侧复制是**块级别快照**,可能捕获到 LV 正在写入的中间状态。 + +如果使用简单的单区覆盖写方案(写入中数据被覆盖),此时: + +``` +zsvA 正在执行 write_metadata(): + 已写入部分新数据,旧数据已被覆盖 + +此刻 sanA → sanB 块级复制发生 + +sanB 上的 LV: + payload 部分损坏,checksum 校验失败 → CORRUPTED + 旧数据已被覆盖 → 不可读 + zsvB DB 无此 VM 记录 → 无法 full-refresh + → VM 不可恢复 ❌ +``` + +**A/B 双 Slot 方案**在同一场景下: + +``` +zsvA 正在执行 write_metadata(): + Phase 1: 标记 PendingOp, ActiveSlot=A 不变 + Phase 2: 正在写入 Slot B (inactive)... + +此刻 sanA → sanB 块级复制发生 + +sanB 上的 LV: + Header: ActiveSlot=A, PendingOp≠0 + Slot A: 完整有效(旧数据,写入过程中未被触碰) + Slot B: 部分损坏 + +zsvB 读取: + Header → ActiveSlot=A → 读 Slot A → checksum pass + → 返回旧元数据 → VM 可注册 ✅ +``` + +**A/B 双 Slot 的核心保证:写入过程中旧数据始终完好。** 这是灾备场景下 VM 可恢复的前提条件,也是本协议采用 A/B Dual Slot 而非更简单方案的根本原因。 + +> **方案选型结论**:单区覆盖写方案(无论是否有 Header 哨兵)在灾备接管场景下都会丢失 VM; +> A/B 双 Slot 是能保证任意复制时刻都有可读数据的最简方案。 +> 协议复杂度是为灾备可靠性买单。 + +--- + +## 2. 设计目标 + +| 目标 | 要求 | +|------|------| +| 原子性 | 任意崩溃点数据不损坏,已提交数据不丢失 | +| 自描述 | Slot 自带位置信息,Header 损坏时仍可恢复 | +| 高效 I/O | O_DIRECT + O_SYNC,对齐到扇区/页边界 | +| 简单可靠 | 纯二进制定长字段,无 JSON 解析开销 | +| 可观测 | hexdump 直接可读,状态可诊断 | +| 前向兼容 | HeaderVersion 管布局演进,SchemaVersion 管 payload 演进 | + +--- + +## 3. 整体架构 + +LV 初始预分配 4MB 空间(虚拟机在正常使用场景下,元数据一般只有几十 KB)。直接以 Raw Data 存储 JSON 元数据,不格式化文件系统,减少性能开销。采用 **预分配固定大小 LV + Raw Data 存储 + A/B 分区原子写** 方案,规避频繁创建/删除 LV 的性能问题。 + +``` +LV Layout (e.g. 4MB) +┌──────────────┬────────────────────┬────────────────────┐ +│ Header Block │ Slot A │ Slot B │ +│ 512B │ ~2MB │ ~2MB │ +│ (pad to 4KB) │ │ │ +└──────────────┴────────────────────┴────────────────────┘ +offset: 0 4096 4096 + SlotACapacity + +空间计算公式(4KB 对齐): +available = LV_SIZE - 4096 +slot_capacity = floor(available / 2 / 4096) * 4096 + +示例(4MB LV): +available = 4194304 - 4096 = 4190208 +slot_capacity = floor(4190208 / 2 / 4096) * 4096 = 2093056 + +Header: [0, 4096) +Slot A: [4096, 2097152) 约 2043 KB +Slot B: [2097152, 4190208) 约 2043 KB +Tail: [4190208, 4194304) 约 4 KB (未使用) +``` + +- **Header Block (512B)**:控制信息,单扇区原子写保证 +- **Slot A / Slot B**:双槽交替写入,A/B 切换实现原子更新 +- **Header 占用前 4KB**:虽然 Header 只有 512B,但为满足 O_DIRECT 对齐要求,Header 后到 Slot A 之间填充零 + +### 3.1 A/B Dual Slot 机制 + +``` +正常状态 (ActiveSlot=A): + 读取 → Slot A (当前有效数据) + +写入时: + Phase 1 → 标记 intent 到 Header + Phase 2 → 写新数据到 Slot B (inactive) + Phase 3 → 切换 ActiveSlot 到 B + 清除 intent + +下次写入: + Phase 1 → 标记 intent + Phase 2 → 写新数据到 Slot A (此时 inactive) + Phase 3 → 切换 ActiveSlot 到 A +``` + +交替写入确保:**任意崩溃点至少有一个 Slot 包含完整有效数据**。 + +### 3.2 版本管理 + +两个独立的版本号,职责分离: + +| 版本号 | 位置 | 含义 | 何时递增 | +|--------|------|------|---------| +| HeaderVersion | Header Block | 二进制布局版本(字段偏移、大小、Checksum 算法) | 增删 Header/Slot 字段时 | +| SchemaVersion | Header Block | Payload JSON 业务 schema 版本 | Payload 中 JSON 字段增减时 | + +读取时: + +- `HeaderVersion > MAX_KNOWN` → 拒绝解析,提示升级软件 +- `SchemaVersion > MAX_KNOWN` → 可读出 payload,但提示部分字段可能无法识别 + +### 3.3 Python 2 兼容性 + +当前 Agent 环境为 Python 2.7(与 ZStack KVM Agent 一致): + +- `struct.pack/unpack` 处理大端序二进制 +- `ctypes` 分配对齐内存缓冲区(O_DIRECT 要求) +- `buffer()` 实现零拷贝写入 +- `hashlib.sha256` (Python 2.7+ 内置) +- 编码时使用 `from __future__ import print_function, unicode_literals` 保持 2/3 兼容 +- `struct.pack`/`hashlib`/`ctypes` 在 Python 2.7+ 和 3.x 行为一致 +- Python 3 迁移随 Agent 整体迁移计划进行,不单独迁移 + +--- + +## 4. 二进制布局 + +### 4.1 Header Block (512 Bytes) + +单扇区大小,硬件层面保证原子写入:写入要么完全成功(全新数据),要么完全未发生(全旧数据),不存在中间状态。 + +#### 4.1.1 字段定义 + +``` +Offset Size Field Type Description +────── ───── ──────────────── ────────── ────────────────────────────────────────── +0 4B Magic uint32 BE 固定 0x5A534D54 ("ZSMT") +4 2B HeaderVersion uint16 BE 二进制格式版本号,当前 = 1 +6 1B ActiveSlot uint8 0 = Slot A,1 = Slot B +7 1B PendingOp uint8 0 = 无,1 = config_update,2 = storage_change +8 8B WriteSequence uint64 BE 单调递增写计数器 +16 8B SlotAOffset uint64 BE Slot A 在 LV 中的字节偏移 +24 8B SlotACapacity uint64 BE Slot A 容量(字节) +32 8B SlotBOffset uint64 BE Slot B 在 LV 中的字节偏移 +40 8B SlotBCapacity uint64 BE Slot B 容量(字节) +48 8B LastUpdateTime uint64 BE 最后成功写入的 epoch 毫秒 +56 4B SchemaVersion uint32 BE Payload JSON schema 版本 +60 4B Reserved uint32 预留,必须写 0 +────── +64B (以上为 Checksum 覆盖范围) +────── +64 32B Checksum raw bytes SHA-256(bytes[0:64]) +96 416B Padding zero 填充至 512B +────── +Total: 512B +``` + +#### 4.1.2 字段设计理由 + +**Magic (4B, offset 0)** +- `0x5A534D54` = ASCII "ZSMT" (ZStack Metadata) +- hexdump 一眼可辨识 +- brute-force 恢复时每个扇区只需读前 4 字节判断 + +**HeaderVersion (2B, offset 4)** +- 二进制布局版本,只在 Header/Slot 结构变更时递增 +- uint16 足够(不可能有 65535 次布局变更) +- 与 SchemaVersion 职责分离:HeaderVersion 管"怎么读",SchemaVersion 管"读出的 JSON 怎么解释" + +**ActiveSlot (1B, offset 6) + PendingOp (1B, offset 7)** +- 各 1B 足够(取值范围 0~2) +- 不用 bit flags:语义清晰,调试简单 +- 紧凑排列,在同一个 8B 对齐块内 + +**WriteSequence (8B, offset 8)** +- uint64,理论上限 ~1.8×10¹⁹ +- 以 1000 次/秒计算,约 5.84 亿年溢出 +- 自然对齐在 offset 8 + +**SlotAOffset / SlotBOffset (各 8B, offset 16/32)** +- **显式存储**,不再通过 `SlotBOffset = ALIGNMENT + SlotACapacity` 间接计算 +- 消除了 SlotACapacity 修改导致 SlotB 定位错误的连锁风险 +- 恢复时直接从 raw Header 提取 offset 即可尝试读 Slot + +**SlotACapacity / SlotBCapacity (各 8B, offset 24/40)** +- uint64 最大值 16EB,远超 64MB LV 上限 +- 两个字段分开存储,为未来非对称 Slot 预留可能 + +**LastUpdateTime (8B, offset 48)** +- epoch 毫秒,uint64 +- 诊断用途:不读 Slot 即可判断最后更新时间 +- 冲突检测:多主脑裂时辅助判断数据新鲜度 + +**SchemaVersion (4B, offset 56)** +- Payload JSON 的业务 schema 版本 +- 读 Header 即可判断是否认识该版本,无需解码整个 Slot +- **编码规则**:将 `dbf.getDbVersion()` 返回的数据库版本字符串(如 `"4.10.12"`)解析为数字组件后压缩为 uint32:`(A << 20) | (B << 10) | C`。例如 `"4.10.12"` → `(4 << 20) | (10 << 10) | 12 = 0x00402C0C = 4205580`。解码:`A = v >> 20`,`B = (v >> 10) & 0x3FF`,`C = v & 0x3FF`。每个组件最大支持 1023 +- Header 中的 SchemaVersion 与 DTO JSON 中的 `schemaVersion` 字符串(`dbf.getDbVersion()`)是同一语义的不同表示,写入时编码、读取时解码 + +**Reserved (4B, offset 60)** +- 将 Checksum 推到 offset 64(8B 对齐) +- 必须写 0,读取时忽略 +- 未来可用于 flags、compression type 等 + +**Checksum (32B, offset 64)** +- SHA-256 of bytes[0:64] +- 覆盖 Checksum 之前的所有字段(含 Reserved 和 Magic) +- **不覆盖 Padding**:Padding 不携带信息,排除在外方便未来使用 Padding 区域扩展字段而不破坏旧版本兼容性 +- 校验逻辑:`sha256(block[0:64]) == block[64:96]` + +**Padding (416B, offset 96)** +- 填充至 512B +- 扩展储备:未来增字段从 Padding 分配,bump HeaderVersion +- **约束**:新增字段的默认值必须与 zero(全 0 字节)语义兼容(旧版本写入的 Padding 为 zero) + +#### 4.1.3 hexdump 示例 + +``` +00000000 5a 53 4d 54 00 01 00 01 00 00 00 00 00 00 00 2a |ZSMT...........*| + ^^^^^^^^^ ^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ + Magic V=1 A=0 P=1 WriteSeq = 42 + +00000010 00 00 00 00 00 00 10 00 00 00 00 00 00 1f e0 00 |................| + ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ + SlotAOffset = 4096 SlotACapacity = 2088960 + +00000020 00 00 00 00 00 1f f0 00 00 00 00 00 00 1f e0 00 |................| + ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ + SlotBOffset = 2093056 SlotBCapacity = 2088960 + +00000030 00 00 01 8e 3a 5b c0 00 00 00 00 02 00 00 00 00 |....:...........| + ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^ + LastUpdate=1709123456000 SchemaV=2 Reserved=0 + +00000040 a1 b2 c3 d4 ... (32 bytes SHA-256 checksum) ... |................| + +00000060 00 00 00 00 ... (padding to 512B) ... |................| +``` + +#### 4.1.4 版本兼容策略 + +``` +读取时: + if header_version > MAX_KNOWN_VERSION: + → 拒绝解析,返回错误,提示升级软件 + + if header_version == 1: + → 用 V1 布局解析(当前方案) + + # 未来 V2 示例: + if header_version == 2: + → Reserved 位置改为 CompressionType + → Padding 区域 offset 96~103 分配给新字段 + → Checksum 范围扩大到 bytes[0:104] +``` + +### 4.2 Slot 结构 + +Slot 是数据搬运工,职责单一:可靠地存取 payload、支持自描述恢复。 + +#### 4.2.1 字段定义 + +``` +Offset Size Field Type Description +─────── ───── ────────────── ────────── ────────────────────────────────── +0 4B Magic uint32 BE 固定 0x5A534454 ("ZSDT") +4 8B SeqNum uint64 BE 写序号,与 Header.WriteSequence 对应 +12 8B SlotOffset uint64 BE 自描述:本 Slot 在 LV 中的字节偏移 +20 8B SlotCapacity uint64 BE 自描述:本 Slot 容量 +28 8B PayloadLen uint64 BE Payload 实际字节数 +─────── +36B (固定 Header 部分) +─────── +36 NB Payload raw bytes 元数据 DTO JSON 明文(systemTags/resourceConfigs 为 per-Resource Base64 编码) +36+N 32B Checksum raw bytes SHA-256(bytes[0:36+N]) +─────── +Total: 36 + N + 32 B +``` + +#### 4.2.2 字段说明 + +| 字段 | 设计理由 | +|------|---------| +| Magic | 标识 Slot 数据块,brute-force 恢复的入口条件 | +| SeqNum | 与 Header.WriteSequence 匹配来判断 Phase 2 是否完成 | +| SlotOffset | Header 损坏时的自描述定位;brute-force 时 `stored_offset == actual_offset` 是强校验 | +| SlotCapacity | 配合 SlotOffset 可重建布局;`SlotA.Offset + SlotA.Capacity` 可定位 SlotB | +| PayloadLen | 8B (uint64),虽然实际不超过 32MB,但保持与其他字段统一的 8B 对齐 | +| Payload | 变长,元数据 DTO JSON(systemTags/resourceConfigs 字段为 per-Resource Base64 编码) | +| Checksum | 尾部放置,SHA-256 覆盖 SlotHeader + Payload 全部内容 | + +#### 4.2.3 Checksum 放尾部的理由 + +| 方案 | 优点 | 缺点 | 结论 | +|------|------|------|------| +| Checksum 在尾部(当前) | 写入自然流程;覆盖全部数据 | 需先读 PayloadLen 才知道 Checksum 位置 | ✅ 采用 | +| Checksum 在 Header 固定位置 | 固定偏移 | 不读 Payload 也无法校验,没有实际收益 | ❌ | +| Header/Payload 双 Checksum | 可先验证 Header | 增加写入复杂度,1MB 优化读已覆盖大多数场景 | ❌ | + +#### 4.2.4 Slot 读取校验清单 + +**正常路径 (strict 模式):** +1. `Magic == 0x5A534454` +2. `SlotOffset == expected_offset` +3. `SlotCapacity == expected_capacity` +4. `PayloadLen > 0` 且 `PayloadLen <= SlotCapacity - 36 - 32` +5. `SHA-256(bytes[0:36+PayloadLen]) == bytes[36+PayloadLen:36+PayloadLen+32]` + +**恢复路径 (relaxed 模式):** +1. `Magic == 0x5A534454` +2. `SlotOffset == actual_read_offset`(验证自描述一致性) +3. 不校验 Capacity(恢复时传入的 capacity 可能是推算的) +4. PayloadLen 范围合理 +5. Checksum 校验通过 + +#### 4.2.5 Slot 结构不做修改的理由 + +| 候选改进 | 结论 | 理由 | +|---------|------|------| +| PayloadLen 缩为 4B | ❌ 不改 | 只省 4B,破坏 8B 对齐 | +| 增加 SlotIndex (A/B 标识) | ❌ 不改 | SeqNum 已够判断顺序,SlotIndex 冗余 | +| 增加 Slot 独立版本号 | ❌ 不改 | Header 的 HeaderVersion 已管控全局布局版本 | + +--- + +## 5. 三阶段原子写入 + +### 5.1 设计原则 + +1. **512B 原子写保证**:Header Block 恰好 512B(一个扇区),硬件保证写入原子性 +2. **Phase 1 不破坏现状**:Phase 1 写入的 Header 必须保留 Active Slot 的完整定位能力 +3. **Phase 3 一次性提交**:ActiveSlot 切换、布局更新、PendingOp 清除在同一个 512B 原子写中完成 +4. **Slot 自描述**:每个 Slot 内嵌位置信息,即使 Header 损坏也可恢复 + +### 5.2 完整流程 + +``` +前置:确定目标 + target_slot = 1 - Header.ActiveSlot + new_seq = Header.WriteSequence + 1 + + 如果 payload 超出当前 Slot 容量: + new_lv_size = calculate_extend_size(current_lv_size, required) + 执行 lvextend + new_layout = calculate_slot_layout(new_lv_size) + 否则: + new_layout = 当前 Header 中的布局(offset + capacity 不变) +``` + +#### 前置步骤 — 确定 op_type(控制面指定) + +> **op_type 由控制面指定**:`@MetadataImpact(CONFIG)` → `op_type = CONFIG_UPDATE (1)`,`@MetadataImpact(STORAGE)` → `op_type = STORAGE_CHANGE (2)`。管理层面通过 `storageStructureChange` 字段贯穿整条消息链(`UpdateVmInstanceMetadataMsg` → `WriteVmMetadataToPrimaryStorageMsg` → Agent command),Agent 收到命令时直接使用该值,无需自行读取旧 payload 做 diff。 + +> **注意**:local/NFS 不使用 op_type(JSON atomic rename 无中间状态),`storageStructureChange` 仅在 sblk 场景下转换为 PendingOp 值。 + +#### Phase 1 — Mark Intent (512B 原子写) + +``` +写入 Header: + Magic = 0x5A534D54 (不变) + HeaderVersion = 当前版本 (不变) + ActiveSlot = 旧值 ← 不切换 + PendingOp = op_type (1 或 2) ← 标记意图 + WriteSequence = new_seq ← 递增 + SlotAOffset = 旧值 ← 不变 + SlotACapacity = 旧值 ← 不变 + SlotBOffset = 旧值 ← 不变 + SlotBCapacity = 旧值 ← 不变 + LastUpdateTime = 旧值 ← 不变 + SchemaVersion = 旧值 ← 不变 + Checksum = SHA-256(bytes[0:64]) + +关键约束:布局字段(Offset/Capacity)全部保持旧值 +理由:确保崩溃后 Active Slot 的定位信息完好 +``` + +#### Phase 2 — Write Payload (可能跨多个扇区) + +``` +目标 Slot = target_slot +使用 new_layout 中的 offset/capacity + +写入 Slot 数据: + SlotHeader: + Magic = 0x5A534454 + SeqNum = new_seq + SlotOffset = new_layout 中目标 slot 的 offset + SlotCapacity = new_layout 中目标 slot 的 capacity + PayloadLen = len(payload) + Payload: + 元数据 DTO JSON(systemTags/resourceConfigs 为 per-Resource Base64) + Checksum: + SHA-256(SlotHeader + Payload) + +写入按 ALIGNMENT(4096) 对齐,零填充 +``` + +#### Phase 3 — Commit (512B 原子写) + +``` +写入 Header: + Magic = 0x5A534D54 (不变) + HeaderVersion = 当前版本 (不变) + ActiveSlot = target_slot ← 切换 + PendingOp = 0 ← 清除 + WriteSequence = new_seq ← 保持 Phase 1 值 + SlotAOffset = new_layout 值 ← 此时更新 + SlotACapacity = new_layout 值 ← 此时更新 + SlotBOffset = new_layout 值 ← 此时更新 + SlotBCapacity = new_layout 值 ← 此时更新 + LastUpdateTime = now() ← 此时更新 + SchemaVersion = 当前 schema 版本 ← 此时更新 + Checksum = SHA-256(bytes[0:64]) + +关键:ActiveSlot 切换 + 布局更新 + PendingOp 清除在同一个 512B 原子写中完成 + 要么全部生效(全新),要么全部未生效(全旧) +``` + +### 5.3 崩溃场景完整分析 + +#### 5.3.1 崩溃分析表 + +| 崩溃点 | Header 状态 | Active Slot | Target Slot | 恢复行为 | 结果 | +|--------|------------|-------------|-------------|----------|------| +| Phase 1 之前 | 旧值,pending=0 | 有效 | 旧/空 | 正常读 Active | ✅ 读旧数据 | +| Phase 1 之后,Phase 2 之前 | pending=op, seq=new, **布局=旧** | 有效(旧布局定位正确) | 旧/空 | 用旧布局找 Target → SeqNum≠new_seq → 回退 Active | ✅ 读旧数据 | +| Phase 2 进行中 | pending=op, seq=new, **布局=旧** | 有效 | 损坏(partial write) | 用旧布局找 Target → Checksum fail → 回退 Active | ✅ 读旧数据 | +| Phase 2 完成,Phase 3 之前 (无 extend) | pending=op, seq=new, **布局=旧** | 有效 | 有效,在旧布局位置 | 用旧布局找 Target → SeqNum==new_seq → 使用新数据 | ✅ 读新数据 | +| Phase 2 完成,Phase 3 之前 (有 extend) | pending=op, seq=new, **布局=旧** | 有效 | 有效,但在新布局位置 | 用旧布局找 Target → 旧位置无有效数据 → 回退 Active | ⚠️ 读旧数据(本次写入丢失) | +| Phase 3 之后 | 全新值,pending=0 | 新 Active 有效 | — | 正常读新 Active | ✅ 读新数据 | + +#### 5.3.2 LV extend + 崩溃场景详细分析 + +**场景:ActiveSlot=1(B),payload 太大触发 extend** + +``` +初始状态: + LV = 4MB + SlotA: offset=4096, cap=2044KB + SlotB: offset=2MiB+4096, cap=2044KB + ActiveSlot = 1 (Slot B) + +写入操作: + target = Slot A (inactive) + extend LV → 8MB + new_layout: SlotA offset=4096, cap=4MB; SlotB offset=4MB+4096, cap=4MB + +Phase 1: 写 Header + PendingOp=op, WriteSeq=new + SlotAOffset=4096, SlotACap=2044KB ← 旧值! + SlotBOffset=2MiB+4096, SlotBCap=2044KB ← 旧值! + +Phase 2: 写 payload 到 Slot A + 使用 new_layout: offset=4096, cap=4MB + +崩溃!Phase 3 未执行 +``` + +**恢复:** +- Header 中 ActiveSlot=1 → 读 Slot B +- SlotBOffset=2MiB+4096(旧值)→ Slot B 数据在该位置 → **定位正确** ✅ +- 读到旧数据,返回 NEED_REPAIR 或 STORAGE_CHANGE_INCOMPLETE + +**对比旧方案(不修复的情况):** +- 旧方案 Phase 1 会写新 capacity → SlotBOffset = 4096+4MB → Slot B 实际数据在 2MiB+4096 → **定位失败** ❌ + +#### 5.3.3 extend 场景丢失写入的权衡 + +**丢失发生条件(必须同时满足):** +1. 本次写入触发了 LV extend +2. 崩溃恰好发生在 Phase 2 完成后、Phase 3 执行前 + +**为什么可以接受:** +- 数据安全:旧数据完整可读,不损失已提交数据 +- 语义正确:Phase 3 未完成 = 事务未提交 = 丢弃未提交数据是正确行为 +- 自动恢复:management plane 检测到 pending_op 后会重试或 repair +- 概率极低:extend 不频繁(4MB→64MB 最多几次),且崩溃恰好卡在极窄窗口 + +**替代方案评估:** + +| 方案 | 可行性 | 问题 | +|------|--------|------| +| Phase 2 写入旧布局位置 | ❌ | 旧容量不够(否则不需要 extend) | +| 四阶段写入(Phase 2.5 更新布局) | ❌ | Phase 2.5 崩溃后回到同样问题 | +| Write Ahead Log | ❌ | 过度设计,复杂度与收益不对等 | + +**结论:接受此场景下的行为,三阶段足够。** + +### 5.4 Header 字段变更对照表 + +| 字段 | Phase 1 | Phase 3 | +|------|---------|---------| +| Magic | 不变 | 不变 | +| HeaderVersion | 不变 | 不变 | +| ActiveSlot | **不变**(旧值) | **切换**(target) | +| PendingOp | **设置**(op_type) | **清除**(0) | +| WriteSequence | **递增**(new_seq) | 不变(保持 new_seq) | +| SlotAOffset | **不变**(旧值) | **更新**(new_layout) | +| SlotACapacity | **不变**(旧值) | **更新**(new_layout) | +| SlotBOffset | **不变**(旧值) | **更新**(new_layout) | +| SlotBCapacity | **不变**(旧值) | **更新**(new_layout) | +| LastUpdateTime | **不变**(旧值) | **更新**(now) | +| SchemaVersion | **不变**(旧值) | **更新**(当前版本) | +| Checksum | 重算 | 重算 | + +--- + +## 6. 读取与恢复流程 + +### 6.1 读取主流程 + +``` +read_metadata(lv_path, lv_size): + + 1. 以 O_DIRECT | O_SYNC 只读打开 LV + 2. 读 Header Block (512B) + 3. 反序列化 + 校验 Header(magic、version、checksum) + + 4. 如果 Header 有效: + 从 Header 直接读取 Slot 定位信息: + slot_a_off = Header.SlotAOffset ← 显式读取,不计算 + slot_a_cap = Header.SlotACapacity + slot_b_off = Header.SlotBOffset ← 显式读取,不计算 + slot_b_cap = Header.SlotBCapacity + + 根据 PendingOp 分支 → Flow A / B / C + + 5. 如果 Header 无效: + → 进入恢复流程(§6.3) +``` + +### 6.2 三种读取分支 + +#### 6.2.1 Flow A — PendingOp = 0 (正常) + +``` +读 Active Slot → 校验通过 → 返回 OK + payload + +如果 Active Slot 校验失败: + 读 Inactive Slot(仅用于诊断) + 如果 Inactive 有效: + → CORRUPTED + 提示"Active 损坏,Inactive 有效但可能是旧数据" + → repair_action: 切换 Active 或 full-refresh + 如果 Inactive 也无效: + → CORRUPTED + "两个 Slot 均损坏" + → repair_action: full-refresh +``` + +#### 6.2.2 Flow B — PendingOp = 1 (CONFIG_UPDATE 中断) + +CONFIG_UPDATE 的特点:旧数据可以安全使用(只是配置过时,不会导致数据损坏)。 + +``` +target_slot = 1 - ActiveSlot + +尝试读 Target Slot: + 如果有效 且 SeqNum == Header.WriteSequence: + → Phase 2 已完成,Phase 3 未完成 + → NEED_REPAIR + target 的 payload(更新的数据) + → repair_action: 完成 Phase 3 + + 否则(Target 无效或 SeqNum 不匹配): + 回退读 Active Slot: + 如果有效: + → NEED_REPAIR + active 的 payload(旧但安全的数据) + → repair_action: 清除 PendingOp + 如果无效: + → CORRUPTED +``` + +#### 6.2.3 Flow C — PendingOp = 2 (STORAGE_CHANGE 中断) + +STORAGE_CHANGE 的特点:存储操作可能已在块设备层面完成,旧元数据描述的存储拓扑与实际不符,**使用旧元数据注册 VM 可能导致数据丢失**。 + +``` +target_slot = 1 - ActiveSlot + +尝试读 Target Slot: + 如果有效 且 SeqNum == Header.WriteSequence: + → Phase 2 已完成,Phase 3 未完成 + → NEED_REPAIR + target 的 payload(新拓扑数据) + → repair_action: 完成 Phase 3 + → 这是安全的,新数据反映了存储变更 + + 否则(Target 无效或 SeqNum 不匹配): + → Phase 2 未完成或数据损坏 + → 旧 Active Slot 的数据已过期,不反映当前存储状态 + + 读 Active Slot(仅用于诊断,标记为 stale): + → STORAGE_CHANGE_INCOMPLETE + → payload = active 的旧数据(标记为 stale) + → is_usable() = False ← 关键:禁止正常使用 + → error: "存储拓扑已变更但元数据未更新,必须执行 full-refresh" + → repair_action: "从数据库重建元数据,执行 full-refresh" +``` + +#### 6.2.4 ReadResult 状态语义 + +| Status | payload | is_usable() | 调用方行为 | +|--------|---------|-------------|-----------| +| OK | ✅ 有效 | True | 正常使用 | +| NEED_REPAIR | ✅ 有效 | True | 使用数据 + 触发后台 repair | +| RECOVERED | ✅ 有效 | True | 使用数据 + 触发 Header 重建 | +| STORAGE_CHANGE_INCOMPLETE | ⚠️ stale 数据 | **False** | **禁止注册 VM**,必须 full-refresh | +| CORRUPTED | ❌ 无 | False | 必须 full-refresh | + +### 6.3 Header 损坏恢复流程 + +当 Header 校验失败(magic 错误、checksum 不匹配、version 不认识)时,进入分层恢复。 + +#### 6.3.1 恢复层次 + +``` +Layer 1: Raw Header 字段提取 + │ Header 512B = 单扇区,即使 Checksum 坏了,字段可能仍可读 + │ 尝试提取 ActiveSlot、SlotAOffset、SlotBOffset 等 + │ 比无 Offset 字段时多了直接定位信息,恢复成功率更高 + │ + ▼ +Layer 2: 布局推算 + │ 用 _calculate_slot_layout(lv_size) 从当前 LV 大小推算 Offset/Capacity + │ 在推算位置尝试读两个 Slot + │ + ▼ +Layer 3: Slot A 自描述辅助定位 Slot B + │ 如果 Layer 2 的 Slot B 位置失败 + │ 从 Slot A 的 SlotOffset + SlotCapacity 推算旧 Slot B 位置 + │ 覆盖 LV extend 后布局变化的情况 + │ + ▼ +Layer 4: Brute-force 扫描 + 最后手段,以 1MB 为单位批量读取 LV,在内存中逐 ALIGNMENT 对齐位置搜索 + 匹配条件:ZSDT Magic + SlotOffset == actual_offset(双重校验,误报极低) + 64MB LV ≈ 64 次 × 1MB 读 ≈ 64MB I/O(顺序读,SSD 场景 <1s) +``` + +#### 6.3.2 恢复时的 Slot 选择策略 + +当找到两个有效 Slot 时: + +``` +优先级: + 1. Raw Header 中的 ActiveSlot hint(如果可提取)→ 使用 hint 指向的 Slot + 2. 无 hint → 使用 SeqNum 更高的 Slot(最后写入的数据更新) + 3. 只有一个有效 → 使用该 Slot + 4. 都无效 → CORRUPTED + +注意:恢复路径使用 relaxed 校验模式 + - 不校验 SlotCapacity(因为传入的 capacity 可能是推算的,与 Slot 自描述不同) + - 依赖 Checksum 作为最终数据完整性裁判 +``` + +#### 6.3.3 Layer 1 详细逻辑 + +``` +读取 Header 原始 512B 数据 + +尝试解析 Magic: + if magic != 0x5A534D54 → 跳过 Layer 1,进入 Layer 2 + +Magic 正确但 Checksum 错误(单 bit 翻转等场景): + 提取各字段作为 hint: + active_slot_hint ← 如果值 ∈ {0, 1} 则可信 + slot_a_off_hint ← 如果值 > 0 且 < lv_size 则可用 + slot_b_off_hint ← 如果值 > slot_a_off_hint 且 < lv_size 则可用 + + 用 hint 的 offset 尝试读 Slot: + 如果成功 → 返回 RECOVERED + 如果失败 → 继续 Layer 2 +``` + +**Layer 1 相比旧方案的改进:** 旧方案 Header 中没有 SlotBOffset,只能从 SlotACapacity 间接推算。新方案 Header 显式存储 SlotAOffset + SlotBOffset,raw 提取后直接可用,减少一步间接计算的出错风险。 + +### 6.4 Slot 读取优化 + +#### 6.4.1 一次读优化 + +``` +optimistic_read_size = min(slot_capacity, 1MB) + +第一次读: 从 slot_offset 读 optimistic_read_size + → 大多数情况下 payload < 1MB,一次读取完成 + +如果 payload + header + checksum > optimistic_read_size: + 第二次读: 从 slot_offset 读 aligned_up(total_needed) + → 仅在极大 payload 时触发 +``` + +#### 6.4.2 strict vs relaxed 校验 + +| 校验项 | 正常路径 (strict) | 恢复路径 (relaxed) | +|--------|-------------------|-------------------| +| Magic == ZSDT | ✅ | ✅ | +| SlotOffset == expected | ✅ | ✅ | +| SlotCapacity == expected | ✅ | ❌ 跳过 | +| PayloadLen 范围合理 | ✅ | ✅ | +| SHA-256 Checksum | ✅ | ✅ | + +--- + +## 7. Repair 与 Full-Refresh + +### 7.1 pending_op 的性质 + +| PendingOp | 含义 | 旧数据安全性 | 可否简单清除 | +|-----------|------|-------------|-------------| +| 0 | 无操作 | — | — | +| 1 (CONFIG_UPDATE) | VM 配置变更中断 | ✅ 旧配置安全可用 | ✅ 可以 | +| 2 (STORAGE_CHANGE) | 存储拓扑变更中断 | ❌ 旧拓扑与实际不符 | ❌ **绝不可以** | + +**核心区别:** CONFIG_UPDATE 的旧数据"过时但安全",STORAGE_CHANGE 的旧数据"过时且危险"。 + +**pending_op 语义说明**: + +- **普通配置更新流程**:用户 API (改CPU/内存) → DB 更新成功 → 写入元数据 (pending=1) +- **存储变更更新流程**:用户 API (创建快照) → 存储操作完成 (快照已创建) → 写入元数据 (pending=2) + +| pending 值 | 含义 | 写入中断的后果 | +|-----------|------|--------------| +| 0 | 空闲,上次写入已完成 | — | +| 1 | 正在写入普通配置变更的元数据 | 丢失一次配置更新,可接受,能恢复 | +| 2 | 正在写入存储变更后的元数据(存储上已有新快照/卷) | 存储上有新数据,但元数据没记录!危险!不能用于注册 VM | + +### 7.2 repair_pending_op 策略 + +#### 7.2.1 CONFIG_UPDATE (pending_op = 1) + +``` +读取 Header → 确认 pending_op = 1 + +计算 target_slot 定位信息(使用 Header 中的旧布局) +读取 Target Slot + +Case A: Target 有效 且 SeqNum == Header.WriteSequence + → Phase 2 已完成,只需完成 Phase 3 + → 写入新 Header: + ActiveSlot = target_slot + PendingOp = 0 + WriteSequence = 保持 + 布局字段 = 保持旧值(因为 Phase 1 没更新布局) + LastUpdateTime = now() + → 返回 repaired=True, "Completed Phase 3" + +Case B: Target 无效 + → Phase 2 未完成(或数据损坏) + → 安全丢弃本次写入,恢复到旧状态 + → 写入新 Header: + ActiveSlot = 保持(旧值) + PendingOp = 0 ← 清除 + WriteSequence = 保持 + 布局字段 = 保持 + LastUpdateTime = 保持 + → 返回 repaired=True, "Aborted incomplete config update" +``` + +#### 7.2.2 STORAGE_CHANGE (pending_op = 2) + +``` +读取 Header → 确认 pending_op = 2 + +计算 target_slot 定位信息(使用 Header 中的旧布局) +读取 Target Slot + +Case A: Target 有效 且 SeqNum == Header.WriteSequence + → Phase 2 已完成,可以安全完成 Phase 3 + → 写入新 Header: + ActiveSlot = target_slot + PendingOp = 0 + WriteSequence = 保持 + 布局字段 = 保持旧值 + LastUpdateTime = now() + → 返回 repaired=True, "Completed Phase 3 for storage change" + +Case B: Target 无效 + → Phase 2 未完成 + → 旧 Active Slot 中的元数据不反映当前存储状态 + → ⚠️ 不清除 PendingOp ← 关键决策 + → 返回 repaired=False, + error="STORAGE_CHANGE pending, target data lost. + Metadata is stale. Must execute full-refresh + from database to rebuild metadata." +``` + +#### 7.2.3 为什么 STORAGE_CHANGE 不能简单清除 PendingOp + +``` +如果清除 pending_op: + Header 变为: pending=0, ActiveSlot=旧 + 后续 read_metadata → 返回 OK + 旧 payload + 调用方认为数据有效 → 用旧拓扑注册 VM + + 但实际存储状态已变更(如:快照已创建/删除、卷已扩容) + 旧拓扑 ≠ 当前存储 → VM 挂载错误的快照链 + → 数据损坏或丢失 +``` + +**PendingOp=2 是一个"脏标记"**:它的存在持续提醒系统"存储状态与元数据不一致"。只有两种方式可以消除这个标记: + +1. **找到有效 Target 完成 Phase 3** — 新元数据反映了存储变更,安全 +2. **Full-refresh 写入全新元数据** — 从数据库重建完整拓扑,覆盖整个 Header + +### 7.3 Full-Refresh 机制 + +#### 7.3.1 触发条件 + +| 场景 | 触发方 | +|------|--------| +| STORAGE_CHANGE_INCOMPLETE | management plane 检测到后主动触发 | +| CORRUPTED(两个 Slot 都损坏) | management plane 检测到后主动触发 | +| repair_pending_op 返回 repaired=False | management plane 收到失败回调后触发 | +| 管理员手动触发 | 运维命令 | + +#### 7.3.2 执行方式 + +Full-refresh 本质上是一次普通的 `write_metadata` 调用: + +``` +full_refresh(lv_path, lv_size_getter, lv_extend_func): + + 1. Management plane 从数据库查询 VM 的完整存储拓扑 + 2. 生成最新的 payload JSON + 3. 调用 write_metadata(lv_path, payload, storageStructureChange=True) + → 控制面显式指定 op_type = STORAGE_CHANGE (2) + + 写入流程: + Phase 1: PendingOp=2, WriteSeq=old+1 + Phase 2: 写入新 payload 到 inactive Slot + Phase 3: ActiveSlot 切换, PendingOp=0 + + 成功后: + - 旧的 STORAGE_CHANGE pending 状态被覆盖 + - 新元数据反映数据库中的最新拓扑 + - 两个 Slot 中至少有一个包含正确数据 +``` + +#### 7.3.3 Full-refresh 使用 STORAGE_CHANGE(2) 的理由 + +Full-refresh 始终使用 STORAGE_CHANGE(2),因为: + +- Full-refresh 由控制面触发,控制面知道这是全量刷新操作,显式指定 `storageStructureChange=true` → op_type=2 +- 这自然解决了"full-refresh Phase 1 覆盖脏标记"问题:新的 PendingOp=2 与旧的语义一致 +- 不需要引入新的 OP_FULL_REFRESH (3) + +#### 7.3.4 Full-refresh 中断的场景 + +``` +如果 full-refresh 本身在 Phase 2 之前崩溃: + Phase 1 写入了 PendingOp=2 + Target 无效 + repair → Case B for STORAGE_CHANGE → 返回 STORAGE_CHANGE_INCOMPLETE + 此时 Active Slot 仍然是旧的 + + 是否有风险? + → management plane 知道 full-refresh 失败了 + (write_metadata 会抛异常),会重试。 + → 重试仍会使用 op=2(控制面显式指定),PendingOp 语义一致。 + → 只要 management plane 正确实现重试逻辑,不会误用旧数据。 +``` + +### 7.4 操作类型决策机制 + +> **op_type 由控制面指定**:管理层面根据 `@MetadataImpact` 注解确定 op_type,通过 `storageStructureChange` 字段传递给 Agent。 + +管理层面调用 `writeMetadata(payload, storageStructureChange)` 时显式指定 op_type。Agent 端直接使用该值: + +- `storageStructureChange = false` → `op_type = CONFIG_UPDATE (1)` → `PendingOp = 1` +- `storageStructureChange = true` → `op_type = STORAGE_CHANGE (2)` → `PendingOp = 2` + +**控制面决策规则**: +- `@MetadataImpact(CONFIG)` 标注的 API(CPU/内存/标签等变更)→ `storageStructureChange = false` +- `@MetadataImpact(STORAGE)` 标注的 API(磁盘挂载/卸载、快照创建/删除等)→ `storageStructureChange = true` +- Full-refresh / 首次写入 → `storageStructureChange = true` +- 多次 `markDirty` 合并时,`storageStructureChange` 采用 OR 升级策略(任一为 true 则结果为 true) + +**好处**: +- 控制面对 op_type 拥有完整语义信息(知道哪个 API 触发了变更) +- Agent 无需读取旧 payload 做 diff,减少一次 I/O +- `VmMetadataDirtyVO` 记录 `storageStructureChange` 字段,Poller 批量处理时直接使用 + +**对 local/NFS 的影响**:local/NFS 不使用 op_type(JSON atomic rename 无中间状态),`storageStructureChange` 仅在 sblk 场景下转换为 PendingOp 值。 + +### 7.5 完整状态转换图 + +``` + ┌──────────────┐ + │ PendingOp=0 │ 正常状态 + │ ActiveSlot=X│ + └──────┬───────┘ + │ + write_metadata() + │ + ┌────────────▼────────────┐ + │ Phase 1 │ + │ PendingOp=1或2 │ + │ WriteSeq=new │ + │ ActiveSlot=X (不变) │ + │ Layout=旧 (不变) │ + └────────────┬─────────────┘ + │ + ┌──────▼──────┐ + ┌───────│ Phase 2 │───────┐ + │ │ Write Slot │ │ + │ └──────┬──────┘ │ + │ │ │ + 崩溃(Target无效) 崩溃(Target有效) 正常 + │ │ │ + ▼ ▼ ▼ + ┌──────────┐ ┌───────────┐ ┌──────────────┐ + │回退到旧 │ │NEED_REPAIR│ │ Phase 3 │ + │Active │ │可用新数据 │ │ Commit │ + └────┬─────┘ └─────┬─────┘ └──────┬───────┘ + │ │ │ + ┌────▼─────┐ ┌─────▼─────┐ ┌──────▼───────┐ + │若op=1: │ │ repair: │ │ PendingOp=0 │ + │清除→OK │ │完成Phase3 │ │ ActiveSlot=Y │ + │若op=2: │ │ │ │ Layout=新 │ + │不清除→ │ └───────────┘ └──────────────┘ + │需refresh │ 正常状态 + └──────────┘ +``` + +--- + +## 8. LV 管理与扩容 + +### 8.1 LV 命名规范 + +``` +格式: {vm_uuid}_vmmeta +示例: a1b2c3d4e5f6_vmmeta +路径: /dev/{vg_uuid}/{vm_uuid}_vmmeta +``` + +扫描规则:遍历 VG 中所有 LV,`lv_name.endswith('_vmmeta')` 即为元数据 LV。 + +### 8.2 LV 大小参数 + +| 参数 | 值 | 说明 | +|------|-----|------| +| 初始大小 | 4 MB | 足够绝大多数 VM 配置 | +| 最大大小 | 64 MB | 防止单 VM 元数据占用过多空间 | +| 对齐粒度 | 4096 B (ALIGNMENT) | 满足 O_DIRECT 对齐要求 | + +### 8.3 LV 内部空间分配 + +``` +calculate_slot_layout(lv_size): + + header_reserved = ALIGNMENT (4096 B) ← Header 512B + padding to 4KB + available = lv_size - header_reserved + slot_capacity = (available / 2) 向下对齐到 ALIGNMENT + + slot_a_offset = header_reserved ← 固定 4096 + slot_a_capacity = slot_capacity + slot_b_offset = header_reserved + slot_capacity + slot_b_capacity = slot_capacity + +示例 (4MB LV): + header_reserved = 4096 + available = 4194304 - 4096 = 4190208 + slot_capacity = (4190208 / 2) 对齐 = 2093056 (≈ 2044 KB) + slot_a_offset = 4096 + slot_b_offset = 4096 + 2093056 = 2097152 +``` + +Slot 最大 payload = slot_capacity - 36 (SlotHeader) - 32 (Checksum) = slot_capacity - 68 + +### 8.4 阶梯扩容策略 + +当 payload 超出当前 Slot 容量时触发 LV 扩容。 + +#### 8.4.1 扩容步长 + +| 当前 LV 大小 | 步长 | +|-------------|------| +| < 8 MB | 2 MB | +| 8 MB ~ 16 MB | 4 MB | +| 16 MB ~ 32 MB | 8 MB | +| > 32 MB | 16 MB | + +#### 8.4.2 阶梯设计理由 + +- 小 LV 用小步长:避免浪费(大多数 VM 的元数据在 4MB 内就够了) +- 大 LV 用大步长:减少扩容次数(快照链很长的 VM 需要更多空间) +- 最大 64MB 上限:超过说明 VM 快照/卷数量异常,应在管理层面限制 + +#### 8.4.3 扩容计算示例 + +``` +场景: 当前 LV=4MB, 需要 slot 容量 3MB + +required_lv = ALIGNMENT + 2 * align_up(3MB + 68B) ≈ 6MB + 4KB +当前 4MB < required 6MB + +step 1: 4MB + 2MB = 6MB → 仍 < 6MB+4KB +step 2: 6MB + 2MB = 8MB → 满足 +→ extend LV to 8MB +``` + +#### 8.4.4 扩容时机 + +``` +write_metadata() 中: + + required = SLOT_HEADER_SIZE + len(payload) + CHECKSUM_SIZE (= 36 + N + 32) + target_cap = Header 中 target slot 的 capacity + + if required > target_cap: + min_lv = ALIGNMENT + 2 * align_up(required, ALIGNMENT) + new_lv = calculate_extend_size(current_lv_size, min_lv) + lv_extend_func(new_lv) + 重新计算 new_layout +``` + +#### 8.4.5 扩容与三阶段写入的交互 + +``` +关键:lvextend 后必须关闭并重新打开 fd + → 确保内核重新读取块设备大小,新增空间对后续 pwrite 可见 + → close(fd) → fd = open(lv_path, O_RDWR | O_DIRECT | O_SYNC) + +布局更新时序: + 扩容后计算 new_layout(新的 offset/capacity) + Phase 1: Header 中 布局字段 = 旧值(不更新) + Phase 2: payload 写入 new_layout 的 target 位置 + Phase 3: Header 中 布局字段 = new_layout(此时更新) + + 崩溃安全:见 §5.3.2 +``` + +#### 8.4.6 容量超限处理 + +``` +如果 required_lv > MAX_LV_SIZE (64MB): + → 抛出异常 + → 提示 "VM 元数据超过 64MB 上限,可能快照/卷数量异常" + → 管理层面应限制: + - 单 VM 快照数量上限 + - 定期清理过期快照 + - 合并快照链 +``` + +### 8.5 LV 生命周期 + +#### 8.5.1 LV 初始化 + +> **设计变更**:LV 初始化时同时写入 Header + 空 Slot A(`payload="{}"`),并执行 O_DIRECT sanity check。 + +```python +def initialize_metadata_lv(lv_path, lv_size): + fd = os.open(lv_path, os.O_RDWR | os.O_DIRECT | os.O_SYNC) + try: + # Step 0: O_DIRECT sanity check + _io_sanity_check(fd) + + layout = calculate_slot_layout(lv_size) + + # Step 1: Build empty payload Slot A + empty_payload = b'{}' + slot_a = build_slot( + magic=SLOT_MAGIC, + seq_num=1, + slot_offset=layout.slot_a_offset, + slot_capacity=layout.slot_a_capacity, + payload=empty_payload + ) + + # Step 2: Write Slot A + write_aligned(fd, layout.slot_a_offset, slot_a) + + # Step 3: Write Header (ActiveSlot=0, WriteSequence=1, PendingOp=0) + header = build_header( + active_slot=0, pending_op=0, write_sequence=1, + slot_a_offset=layout.slot_a_offset, + slot_a_capacity=layout.slot_a_capacity, + slot_b_offset=layout.slot_b_offset, + slot_b_capacity=layout.slot_b_capacity, + last_update_time=0, schema_version=0 + ) + write_aligned(fd, 0, header) + finally: + os.close(fd) +``` + +**O_DIRECT sanity check**: + +```python +def _io_sanity_check(fd): + """Verify O_DIRECT I/O path works correctly. + + 注意:sanity check 将测试数据写入 offset 0,后续 initialize_metadata_lv() + 会在同一位置写入正式 Header,自然覆盖测试数据。如果 Header 写入失败, + offset 0 处残留测试数据(Magic ≠ 0x5A534D54),读取时将进入恢复流程 + (预期行为:初始化未完成 = 无有效元数据)。 + """ + test_data = b'ZSMT_IO_CHECK' + b'\x00' * (512 - 13) + with AlignedBuffer(512) as buf: + buf.fill(test_data) + buf.pwrite(fd, 0) + with AlignedBuffer(512) as buf: + buf.pread(fd, 0) + if buf.read(13) != b'ZSMT_IO_CHECK': + raise MetadataIOError("O_DIRECT sanity check failed on %s" % lv_path) +``` + +- sanity check 失败 → 抛异常 → 管理层面将此 PS 标记为"不支持元数据" → 该 PS 上所有 VM 静默跳过元数据写入 +- 首次 `read_metadata()` → 返回 `OK` with `payload="{}"`(而非 `CORRUPTED`),避免首次读取返回 CORRUPTED 与真正损坏混淆 + +> **前提条件**:sblk metadata 要求底层块设备支持 512B 扇区原子写,这与 LVM 自身的元数据更新机制使用同一保证。 +> 如果 LVM 能在该存储上正常工作,则此前提已满足。 + +#### 8.5.2 LV 删除 + +``` +delete_metadata(lv_path, lv_delete_func): + 直接调用 lv_delete_func(lv_path) 删除整个 LV + 无需清理内部数据 +``` + +#### 8.5.3 LV 扫描 + +``` +scan_metadata_lvs(vg_path, lv_list_func): + 遍历 VG 中所有 LV + 筛选 lv_name.endswith('_vmmeta') + 返回 [{vm_uuid, lv_path, lv_size}, ...] +``` + +### 8.6 健康检查 + +``` +get_metadata_status(lv_path): + 只读打开 LV → 读 Header 512B → 校验 → 返回摘要 + + 返回值: + { + valid: bool + header_version: int + active_slot: int + pending_op: int + write_sequence: int + slot_a_offset: int + slot_a_capacity: int + slot_b_offset: int + slot_b_capacity: int + last_update_time: int + schema_version: int + } + + 用途: + - 运维巡检 + - 监控告警(pending_op != 0 持续时间过长) + - 诊断工具展示 +``` + +--- + +## 9. I/O 与字节序技术细节 + +### 9.1 字节序 + +所有多字节整数字段统一使用**大端序(Big Endian)**。Python 2 中使用 `struct.pack('>I', magic)` / `struct.pack('>H', version)` / `struct.pack('>Q', seq_num)` 等。 + +选择大端序的理由: + +- 大端序是网络字节序,是跨平台数据交换的惯例 +- Magic Number `0x5A534D54` 在大端序下直接对应 ASCII "ZSMT",便于 hexdump 调试 +- LVM 元数据本身也使用大端序 + +### 9.2 SHA-256 输出格式 + +使用 **32 字节二进制**(非 64 字符十六进制字符串)。在 Python 2 中使用 `hashlib.sha256(data).digest()` 得到 32 字节 bytes。 + +### 9.3 O_DIRECT 与扇区原子写 + +**512B 扇区原子写在 LVM + 分布式存储场景下的可靠性:** + +| 存储层 | 保证 | +|--------|------| +| 本地 SCSI/SATA/NVMe 磁盘 | 单扇区(512B 或 4KB)原子写是 ATA/SCSI 标准保证 | +| LVM Logical Volume | LV 底层最终映射到物理设备的扇区,LVM 层不会拆分对齐的扇区写入 | +| SAN (iSCSI/FC) | SCSI 协议保证单扇区原子写 | +| 分布式块存储(如 sblk 底层) | 取决于具体实现,但通常遵循块设备语义 | + +**前提条件:** +- 写入对齐到扇区边界(Header 从 offset 0 开始,天然对齐) +- 使用 O_DIRECT 或 O_SYNC 确保不被 page cache 合并/拆分 + +**所有 sblk 元数据读写统一使用 `O_DIRECT | O_SYNC`**,包括 Header 和 Slot。理由: + +- sblk 是共享块设备,多节点可能访问同一 LV,page cache 会导致不一致 +- 元数据操作频率低(每次 API 后一次),性能开销可忽略 +- 代码路径统一,减少 bug 风险 +- 10MB O_DIRECT 顺序写入延迟:SSD 场景约 50ms,SAN 场景约 200ms,可接受 + +### 9.4 O_DIRECT 内存对齐 + +O_DIRECT 要求用户态 buffer 地址对齐到逻辑扇区大小(通常 512B)。`ctypes.create_string_buffer` 分配的内存**不保证**特定对齐。 + +**解决方案**:使用 `posix_memalign` + `ctypes` 封装为 `AlignedBuffer` 类。写入时 data 长度必须是 512B 的倍数。 + +```python +import ctypes +import os + +_libc = ctypes.CDLL('libc.so.6', use_errno=True) + +class AlignedBuffer(object): + """Page-aligned buffer for O_DIRECT I/O. Use as context manager.""" + + def __init__(self, size, alignment=4096): + self._alignment = alignment + self._size = ((size + alignment - 1) // alignment) * alignment + self._ptr = ctypes.c_void_p() + ret = _libc.posix_memalign( + ctypes.byref(self._ptr), alignment, self._size) + if ret != 0: + raise OSError(ret, "posix_memalign failed") + # Zero-fill + ctypes.memset(self._ptr, 0, self._size) + + def fill(self, data, offset=0): + """Copy data into buffer at given offset.""" + ctypes.memmove(self._ptr.value + offset, data, len(data)) + + def read(self, length, offset=0): + """Read bytes from buffer.""" + return ctypes.string_at(self._ptr.value + offset, length) + + def pwrite(self, fd, file_offset): + """Write buffer contents to fd at file_offset using pwrite.""" + ret = _libc.pwrite(fd, self._ptr, self._size, + ctypes.c_longlong(file_offset)) + if ret < 0: + errno = ctypes.get_errno() + raise OSError(errno, "pwrite failed: " + os.strerror(errno)) + + def pread(self, fd, file_offset): + """Read from fd at file_offset into buffer using pread.""" + ret = _libc.pread(fd, self._ptr, self._size, + ctypes.c_longlong(file_offset)) + if ret < 0: + errno = ctypes.get_errno() + raise OSError(errno, "pread failed: " + os.strerror(errno)) + + def close(self): + if self._ptr.value: + _libc.free(self._ptr) + self._ptr = ctypes.c_void_p() + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __del__(self): + self.close() +``` + +**使用示例**: + +```python +# 写入 Header (512B) +with AlignedBuffer(512) as buf: + buf.fill(header_bytes) + buf.pwrite(fd, 0) + +# 读取 Slot (up to 1MB optimistic) +with AlignedBuffer(1 * 1024 * 1024) as buf: + buf.pread(fd, slot_offset) + data = buf.read(expected_size) + +# 写入 Slot — 构造函数自动将 size 向上对齐到 alignment(4096) +# 注意:pwrite 始终写入 self._size(对齐后)字节,创建时应精确传入所需大小 +slot_total = SLOT_HEADER_SIZE + len(payload) + CHECKSUM_SIZE # 36 + N + 32 +with AlignedBuffer(slot_total) as buf: # e.g. 36+1000+32=1068 → 自动对齐为 4096 + buf.fill(slot_bytes) + buf.pwrite(fd, slot_offset) +``` + +### 9.5 文件锁 + +**sblk 不使用文件锁**:共享块设备上 `fcntl.flock` 语义取决于具体实现(device-mapper + cluster),不可靠。sblk 场景的并发保护完全依赖管理平面的四层串行化机制(见 Part 2 §3.1)。即使毫秒级窗口的并发写入,因全量覆盖写语义,后者覆盖前者,结果依然正确(最终一致性)。无需引入额外分布式锁机制。 + +**local/NFS 使用 flock 作为 defense-in-depth**:`fcntl.flock(fd, LOCK_EX | LOCK_NB)` 在本地文件系统和 NFS 上语义可靠,作为额外安全网防御编程错误或异常并发。`LOCK_NB` 非阻塞:获取失败立即报错(不应发生的情况)。 diff --git a/docs/design/vm-metadata-05-api.md b/docs/design/vm-metadata-05-api.md new file mode 100644 index 00000000000..b53eeabdcd7 --- /dev/null +++ b/docs/design/vm-metadata-05-api.md @@ -0,0 +1,403 @@ +# API 设计 + +## 1. 概述 + +本文档定义虚拟机元数据功能对外暴露的所有 API。按职责分为三类: + +| 类别 | 通信模式 | API | +|------|----------|-----| +| **元数据查询** | 同步 GET → Reply | `APIGetVmInstanceMetadataFromPrimaryStorageMsg`、`APIReadVmInstanceMetadataFromPrimaryStorageMsg` | +| **注册操作** | 异步 POST/PUT → Event | `APIRegisterVmInstanceMsg` | +| **运维诊断** | 异步 PUT → Event(仅 CLI) | `APICheckVmInstanceMetadataConsistencyMsg`、`APIUpdateVmMetadataMsg`、`APIPreCheckVmMetadataRegistrationMsg` | + +**ZStack REST 惯例** + +| HTTP 方法 | 消息基类 | 响应 | 语义 | +|-----------|----------|------|------| +| GET | `APISyncCallMessage` → `APIReply` | 同步返回 | 只读查询 | +| POST | `APIMessage` → `APIEvent` | 异步(轮询/WebSocket) | 创建资源 | +| PUT + `isAction=true` | `APIMessage` → `APIEvent` | 异步 | 非幂等操作/动作 | + +所有 API 仅 **admin** 账户可调用。 + +--- + +## 2. 获取主存储上的虚拟机元数据列表 + +列出指定主存储上所有虚拟机元数据文件的概要信息(VM 名称、UUID、文件路径),用于在注册前了解存储上有哪些可恢复的 VM。 + +### 2.1 请求 + +```java +@RestRequest( + path = "/primary-storage/vm-instances/metadata", + method = HttpMethod.GET, + responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class +) +public class APIGetVmInstanceMetadataFromPrimaryStorageMsg + extends APISyncCallMessage implements PrimaryStorageMessage { + @APIParam(resourceType = PrimaryStorageVO.class) + private String uuid; // 主存储 UUID +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `uuid` | String | 是 | 目标主存储 UUID | + +### 2.2 响应 + +```java +@RestResponse(allTo = "all") +public class APIGetVmInstanceMetadataFromPrimaryStorageReply extends APIReply { + private List vmInstanceMetadatas; +} +``` + +```java +public class VmInstanceMetadataStruct { + private String name; // 虚拟机名称(从元数据文件 DTO 中提取) + private String uuid; // 虚拟机 UUID + private String path; // 元数据文件路径 +} +``` + +**path 返回值示例** + +| 存储类型 | 示例 | +|----------|------| +| sblk | `/dev/{vg_uuid}/{vm_uuid}_vmmeta` | +| local/NFS | `/vms_ds/rootVolumes/acct-{id}/vol-{uuid}/{vm_uuid}_vmmeta` | + +### 2.3 设计要点 + +1. **一次性返回所有**:不分页。元数据文件数量与 VM 数量相当,通常不超过数千个。每个 `VmInstanceMetadataStruct` 仅包含 3 个字符串字段(约 200B),1000 个 VM 的响应 payload 约 200KB,对 HTTP 传输无压力。 +2. **不判断元数据好坏**:扫描阶段只列出文件,不读取 Slot payload、不校验 Checksum。元数据是否损坏在注册时才做完整校验。 +3. **name/uuid 提取方式**: + - **sblk**:读取 Header(512B)验证 Magic 有效后,读取 Active Slot 的前 1MB,解析 payload JSON 提取 `vm.vo` 中的 `name` 和 `uuid` 字段。如果 Header 无效或 Slot 损坏,`name` 返回 `null`,`uuid` 从 LV 名称中提取(`{vm_uuid}_vmmeta` → `vm_uuid`)。 + - **local/NFS**:读取 JSON 文件,解析提取 `vm.vo` 中的 `name` 和 `uuid`。如果文件损坏,`name` 返回 `null`,`uuid` 从文件名提取。 +4. **同步 API**(`APISyncCallMessage`):扫描操作为只读、无副作用,结果集有限,适合同步返回。 + +> **历史说明**:本 API 取代了早期设计的 `APIScanVmMetadataOnPrimaryStorageMsg`。原设计提供分页参数(`start`/`limit`),经评估后改为一次性返回——元数据文件数量有限,分页增加了客户端复杂度但无显著收益。 + +--- + +## 3. 获取指定虚拟机元数据详情 + +从主存储上读取指定 VM 的元数据文件内容并返回,用于运维诊断和灾难恢复前的数据检查。 + +### 3.1 请求 + +```java +@RestRequest( + path = "/primary-storage/{primaryStorageUuid}/vm-instances/{vmUuid}/metadata", + method = HttpMethod.GET, + responseClass = APIReadVmInstanceMetadataFromPrimaryStorageReply.class +) +public class APIReadVmInstanceMetadataFromPrimaryStorageMsg + extends APISyncCallMessage implements PrimaryStorageMessage { + @APIParam(resourceType = PrimaryStorageVO.class) + private String primaryStorageUuid; + + @APIParam + private String vmUuid; // 目标 VM UUID +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `primaryStorageUuid` | String | 是 | 主存储 UUID | +| `vmUuid` | String | 是 | 虚拟机 UUID(用于定位元数据文件) | + +### 3.2 响应 + +```java +@RestResponse(allTo = "all") +public class APIReadVmInstanceMetadataFromPrimaryStorageReply extends APIReply { + private String metadata; // 元数据 DTO JSON 全文(明文,systemTags/resourceConfigs 为 Base64) +} +``` + +### 3.3 设计要点 + +1. **返回原始 JSON 全文**:控制面从存储读取后直接返回(调用 `MetadataStorageHandler.readMetadata()`),不做反序列化或结构变换。调用方(CLI/运维工具)可自行解析 JSON、解码 Base64 字段。 +2. **替代 zstack-ctl 直接读取**:核心设计([Part 1](vm-metadata-01-design.md) §3)原提到"VO、快照等主体数据保持明文,zstack-ctl 可直接读取,无需解码"。实际上 sblk 场景下 zstack-ctl 无法直接读取 LV 上的二进制 Slot 数据。本 API 提供统一的读取入口,屏蔽底层存储差异(sblk 二进制协议 vs local/NFS JSON 文件),所有存储类型通过同一 API 获取明文 JSON。 +3. **同步 API**:读取单个 VM 元数据(通常 <1MB),延迟在百毫秒级,适合同步返回。 +4. **sblk 读取行为**:调用 [Part 4](vm-metadata-04-sblk.md) §6 读取流程(`read_metadata`),如果返回 `NEED_REPAIR` 或 `RECOVERED` 状态,仍返回可用 payload(`is_usable() == True`)。如果 `CORRUPTED` 或 `STORAGE_CHANGE_INCOMPLETE`,返回 `METADATA_CHECKSUM_MISMATCH` 错误。 +5. **local/NFS 读取行为**:读取 JSON 文件 + `_checksum` 校验。校验失败返回 `METADATA_CHECKSUM_MISMATCH`。 + +--- + +## 4. 注册虚拟机 + +从主存储上的元数据文件恢复注册虚拟机。异步操作,涉及 DB 写入、变基、校验等多步骤。 + +### 4.1 请求 + +```java +@RestRequest( + path = "/vm-instances/register", + method = HttpMethod.POST, + responseClass = APIRegisterVmInstanceEvent.class, + parameterName = "params" +) +public class APIRegisterVmInstanceMsg extends APIMessage implements PrimaryStorageMessage { + @APIParam + private String metadataPath; + + @APIParam(resourceType = PrimaryStorageVO.class) + private String primaryStorageUuid; + + @APIParam(resourceType = ClusterVO.class) + private String clusterUuid; + + @APIParam(resourceType = ZoneVO.class) + private String zoneUuid; + + @APIParam(required = false) + private boolean forceVersionMismatch = false; + + @APIParam(required = false, resourceType = HostVO.class) + private String hostUuid; +} +``` + +| 参数 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| `metadataPath` | String | 是 | — | 元数据文件路径(来自 §2 API 返回的 `path` 字段) | +| `primaryStorageUuid` | String | 是 | — | 目标主存储 UUID | +| `clusterUuid` | String | 是 | — | 目标集群 UUID | +| `zoneUuid` | String | 是 | — | 目标区域 UUID | +| `forceVersionMismatch` | boolean | 否 | false | 为 true 时忽略 schemaVersion 不匹配强制注册(缺失字段置 null) | +| `hostUuid` | String | 否 | null | 指定注册后首次启动的目标 Host(不指定则由调度器决定) | + +### 4.2 响应 + +```java +@RestResponse(allTo = "inventory") +public class APIRegisterVmInstanceEvent extends APIEvent { + private VmInstanceInventory inventory; + private List warnings; // 注册过程中的非致命警告列表 +} +``` + +### 4.3 设计要点 + +1. **异步 API**(`APIMessage` + `APIEvent`):注册涉及读取元数据、校验、DB 多表写入、快照链变基等重操作,耗时可达数分钟,必须异步。 +2. **`metadataPath`** 由 §2 API 返回,用户无需手动拼接路径。 +3. **`forceVersionMismatch`** 对应 [Part 1](vm-metadata-01-design.md) §6.3 的版本兼容规则:默认 `false` 时 schemaVersion(`dbf.getDbVersion()`)不匹配直接拒绝;`true` 时允许跨版本注册(缺失字段置 null)。 +4. **`zoneUuid`** 是必填参数,用于替换元数据中的 `VmInstanceVO.zoneUuid`(跨环境注册时原 Zone 不存在)。 +5. **`clusterUuid`** 用于确定可用 Host 范围。注册后 VM 状态为 Stopped,`clusterUuid` 赋值到 `VmInstanceVO.clusterUuid`(有助于首次启动调度)。 +6. **`hostUuid`** 可选参数,指定后变基操作在该 Host 上执行,也作为首选启动 Host。不指定时由系统选择 cluster 内可用 Host。 +7. **`warnings`** 列表包含非致命提示:schemaVersion 不匹配(force=true 场景)、模板 VM 缓存需首次创建等。 +8. **仅 admin 账户**可调用(API 授权层控制)。 + +**完整注册流程**详见 [Part 3: 注册与运维](vm-metadata-03-registration.md) §3。 + +--- + +## 5. 检查虚拟机元数据一致性 + +对比数据库中的 VM 状态与存储上的元数据文件,检查是否一致。仅 CLI 开放,UI 不展示。 + +### 5.1 请求 + +```java +@RestRequest( + path = "/vm-instances/{uuid}/consistency", + method = HttpMethod.PUT, + responseClass = APICheckVmInstanceMetadataConsistencyEvent.class, + isAction = true +) +public class APICheckVmInstanceMetadataConsistencyMsg + extends APIMessage implements VmInstanceMessage { + @APIParam(resourceType = VmInstanceVO.class) + private String uuid; +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `uuid` | String | 是 | 虚拟机 UUID | + +### 5.2 响应 + +```java +public class APICheckVmInstanceMetadataConsistencyEvent extends APIEvent { + private boolean consistent; + private String vmMetadataInDb; // 从 DB 构建的元数据 JSON(不一致时返回) + private String vmMetadataInStorage; // 从存储读取的元数据 JSON(不一致时返回) +} +``` + +### 5.3 行为说明 + +| 场景 | consistent | vmMetadataInDb | vmMetadataInStorage | +|------|-----------|----------------|---------------------| +| 一致 | `true` | null | null | +| 不一致 | `false` | DB 侧元数据 JSON 全文 | 存储侧元数据 JSON 全文 | +| 存储上无元数据文件 | `false` | DB 侧元数据 JSON 全文 | null | + +### 5.4 设计要点 + +1. **异步 API**(`APIMessage` + `APIEvent`):需要从存储异步读取元数据文件。PUT + `isAction=true` 符合 ZStack 非幂等操作的 REST 惯例。 +2. **比较逻辑**:从 DB 调用 `buildVmInstanceMetadata()` 生成一份 → 从存储调用 `MetadataStorageHandler.readMetadata()` 读取一份 → 结构化比较。比较时排除 `lastOpDate`、`id`、`managementNodeUuid` 字段(见 [Part 3](vm-metadata-03-registration.md) §7.2)。 +3. **一致时不返回 JSON**:节省网络开销,调用方只需判断 `consistent` 即可。 +4. **不一致时返回完整 JSON**:调用方可使用外部 diff 工具分析差异。JSON 为明文格式,其中 systemTags/resourceConfigs 字段仍为 Base64 编码,需调用方自行解码查看。 +5. **仅 CLI 使用**:该 API 用于运维诊断,不在 UI 中暴露。可配合 `zstack-cli` 使用。 + +--- + +## 6. 运维辅助 API(仅 CLI) + +### 6.1 手动触发元数据更新 + +```java +@RestRequest( + path = "/vm-instances/{uuid}/actions", + method = HttpMethod.PUT, + responseClass = APIUpdateVmMetadataEvent.class, + isAction = true +) +public class APIUpdateVmMetadataMsg extends APIMessage implements VmInstanceMessage { + @APIParam(resourceType = VmInstanceVO.class) + private String uuid; +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `uuid` | String | 是 | 虚拟机 UUID | + +指定 vmUuid,手动触发一次全量元数据更新。用于达到最大重试次数后的手动恢复,或升级后单独更新指定 VM 的元数据。 + +详见 [Part 3](vm-metadata-03-registration.md) §7.4。 + +### 6.2 注册预检查 + +```java +@RestRequest( + path = "/vm-instances/metadata/precheck", + method = HttpMethod.PUT, + responseClass = APIPreCheckVmMetadataRegistrationEvent.class, + isAction = true +) +public class APIPreCheckVmMetadataRegistrationMsg extends APIMessage { + @APIParam + private String metadataPath; + + @APIParam(resourceType = PrimaryStorageVO.class) + private String primaryStorageUuid; +} +``` + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `metadataPath` | String | 是 | 元数据文件路径 | +| `primaryStorageUuid` | String | 是 | 主存储 UUID | + +预检查注册条件(UUID 冲突、PS 可达性、版本兼容等),返回检查结果列表。不执行实际注册操作。 + +**检查项** + +| # | 检查项 | 条件 | 失败错误码 | 级别 | +|---|--------|------|-----------|------| +| 1 | VM UUID 唯一性 | `!exists(VmInstanceVO, uuid)` | `REG.UUID_CONFLICT` | ERROR | +| 2 | Volume UUID 唯一性 | `!exists(VolumeVO, uuid) for all` | `REG.UUID_CONFLICT` | ERROR | +| 3 | Snapshot UUID 唯一性 | `!exists(VolumeSnapshotVO, uuid) for all` | `REG.UUID_CONFLICT` | ERROR | +| 4 | PS 可达性 | `PS.status == Connected` | `REG.PS_NOT_AVAILABLE` | ERROR | +| 5 | PS 类型兼容 | `type ∈ {SharedBlock, LocalStorage, NFS}` | `REG.PS_INCOMPATIBLE` | ERROR | +| 6 | 跨存储校验 | 所有 Volume 归属同一目标 PS | `REG.CROSS_STORAGE` | ERROR | +| 7 | 存储路径可映射 | 前缀锚定替换后 Agent 验证路径存在 | `REG.PATH_UNMAPPABLE` | ERROR | +| 8 | Schema 版本兼容 | `≤ MAX_SUPPORTED_SCHEMA_VERSION` | `REG.SCHEMA_INCOMPATIBLE` | ERROR | +| 9 | Host 资源充足 | 至少一台 Host 满足 cpu/memory(考虑 overcommit ratio) | `REG.NO_CAPABLE_HOST` | WARNING | + +**辅助检查项** + +| 检查项 | 检查方式 | 级别 | +|--------|----------|------| +| 磁盘文件可读性 | Agent `qemu-img info` | WARNING | +| architecture 兼容性 | 检查目标 Zone 集群 | WARNING | +| hypervisorType 兼容性 | 检查目标 Zone hypervisor | WARNING | +| 元数据完整性 | Validator 全量校验 | ERROR | +| imageUuid 存在性 | 查询 ImageVO | WARNING | + +> 预检查是"尽力而为"的辅助工具,不作为注册前置依赖。注册流程内部有自己的校验。 + +详见 [Part 3](vm-metadata-03-registration.md) §7.3。 + +--- + +## 7. API 汇总 + +| API | HTTP | 类型 | 权限 | 场景 | +|-----|------|------|------|------| +| `APIGetVmInstanceMetadataFromPrimaryStorageMsg` | `GET /primary-storage/vm-instances/metadata` | 同步 | admin | 列出存储上所有 VM 元数据概要 | +| `APIReadVmInstanceMetadataFromPrimaryStorageMsg` | `GET /primary-storage/{psUuid}/vm-instances/{vmUuid}/metadata` | 同步 | admin | 读取指定 VM 的完整元数据 JSON | +| `APIRegisterVmInstanceMsg` | `POST /vm-instances/register` | 异步 | admin | 从元数据注册恢复虚拟机 | +| `APICheckVmInstanceMetadataConsistencyMsg` | `PUT /vm-instances/{uuid}/consistency` | 异步 | admin(CLI) | 一致性检查 | +| `APIUpdateVmMetadataMsg` | `PUT /vm-instances/{uuid}/actions` | 异步 | admin(CLI) | 手动触发元数据全量更新 | +| `APIPreCheckVmMetadataRegistrationMsg` | `PUT /vm-instances/metadata/precheck` | 异步 | admin(CLI) | 注册预检查(不执行注册) | + +--- + +## 8. 设计合理性分析 + +### 8.1 同步/异步选择 + +| API | 选择 | 理由 | +|-----|------|------| +| 获取元数据列表 | 同步 GET | 只读扫描,结果集有限(<200KB),无副作用 | +| 读取指定 VM 元数据 | 同步 GET | 读取单个文件(<1MB),延迟百毫秒级 | +| 注册虚拟机 | 异步 POST | 多步骤重操作(读取→校验→DB写入→变基),耗时可达数分钟 | +| 一致性检查 | 异步 PUT | 需异步读取存储文件 + 结构化比较 | +| 手动更新元数据 | 异步 PUT | 需序列化→写入存储→校验 | +| 注册预检查 | 异步 PUT | 需访问 Agent 验证路径存在性等 | + +### 8.2 不分页的合理性 + +元数据文件数量与 VM 数量 1:1 对应。单个 `VmInstanceMetadataStruct` 约 200B,1000 VM 的响应 ≈ 200KB,万级 VM 场景 ≈ 2MB,仍在 HTTP 响应合理范围内。分页增加客户端轮询复杂度(需 do-while 循环累加),对产品形态(CLI 列表展示)无额外收益。 + +### 8.3 一致性检查返回格式 + +返回 DB 侧和存储侧的完整 JSON 字符串,而非结构化 diff 对象。理由: + +- 该 API 面向 CLI 运维,调用方可使用 `jq`、`diff` 等外部工具分析差异 +- 结构化 diff 增加 API 复杂度(需定义 DiffEntry 结构、处理嵌套集合差异),但该 API 仅用于诊断,调用频率极低,投入产出比不高 +- 一致时不返回 JSON,节省 95%+ 场景的网络开销 + +### 8.4 新增 Read API 的必要性 + +核心设计 [Part 1](vm-metadata-01-design.md) §3 原提到"VO、快照等主体数据保持明文,zstack-ctl 可直接读取"。但实际场景中: + +- **sblk 存储**:元数据存储在 LV 中的二进制 Slot 格式,zstack-ctl 无法直接读取 +- **权限隔离**:zstack-ctl 运行在 MN 节点,不一定有 Host 侧存储的直接访问权限 +- **一致性入口**:统一通过 `MetadataStorageHandler` 读取,自动处理 Slot 选择、Checksum 校验、损坏修复 + +因此新增 `APIReadVmInstanceMetadataFromPrimaryStorageMsg` 作为唯一的元数据读取入口,屏蔽底层存储差异。 + +### 8.5 现有代码与设计的差距 + +| 项目 | 现有代码 | 设计要求 | 说明 | +|------|----------|----------|------| +| `APIRegisterVmInstanceMsg` 字段 | `metadataPath`, `primaryStorageUuid`, `clusterUuid`, `hostUuid` | 增加 `zoneUuid`(必填)、`forceVersionMismatch` | 跨环境注册需要 zoneUuid;版本兼容需要 force 参数 | +| `APIRegisterVmInstanceReply` 命名 | 类名为 `Reply` 但 `extends APIEvent` | 建议改为 `APIRegisterVmInstanceEvent` | 符合异步 API → Event 的 ZStack 命名惯例 | +| `APIGetVmInstanceMetadataFromPrimaryStorageReply` 返回类型 | `List vmInstanceMetadata` | `List vmInstanceMetadatas` | 结构化返回,含 name/uuid/path | +| `APIReadVmInstanceMetadataFromPrimaryStorageMsg` | 不存在 | 需新建 | 替代 zstack-ctl 直接读取 | +| `APICheckVmInstanceMetadataConsistencyMsg` | 不存在 | 需新建 | 一致性诊断 | + +--- + +## 9. 错误码 + +| 错误码 | 含义 | 触发 API | +|--------|------|----------| +| `METADATA_FILE_NOT_FOUND` | 元数据文件不存在 | Read、Register | +| `METADATA_CHECKSUM_MISMATCH` | SHA256 校验失败 | Read、Register、Consistency | +| `METADATA_VERSION_MISMATCH` | schemaVersion 不匹配(force=false) | Register | +| `METADATA_UUID_CONFLICT` | UUID 与现有资源冲突 | Register、PreCheck | +| `METADATA_CROSS_STORAGE` | 磁盘分布在不同主存储 | Register、PreCheck | +| `METADATA_STORAGE_UNREACHABLE` | 存储路径不可达 | 所有 API | +| `METADATA_REBASE_FAILED` | 快照链变基失败 | Register | +| `METADATA_STORAGE_NOT_SUPPORTED` | 存储类型不支持(ceph/zbs/vhost) | 所有 API | +| `METADATA_BASE64_DECODE_FAILED` | Base64 解码失败 | Register | diff --git a/docs/design/zstack-api-catalog.md b/docs/design/zstack-api-catalog.md new file mode 100644 index 00000000000..3cba513f408 --- /dev/null +++ b/docs/design/zstack-api-catalog.md @@ -0,0 +1,3729 @@ +# ZStack API 清单 + +> 自动生成自 `ApiHelper.groovy` 及仓库源码扫描 +> 生成日期:2026-03-02 +> API 消息总数:1949 | 模块数:83 | 包数:230 + +--- + +## 目录 + +1. [core](#module-core) (16 APIs) +2. [header](#module-header) (367 APIs) +3. [plugin/account-import](#module-pluginaccount-import) (1 APIs) +4. [plugin/acl](#module-pluginacl) (7 APIs) +5. [plugin/applianceVm](#module-pluginappliancevm) (1 APIs) +6. [plugin/ceph](#module-pluginceph) (15 APIs) +7. [plugin/directory](#module-plugindirectory) (8 APIs) +8. [plugin/eip](#module-plugineip) (9 APIs) +9. [plugin/flatNetworkProvider](#module-pluginflatnetworkprovider) (3 APIs) +10. [plugin/hostNetworkInterface](#module-pluginhostnetworkinterface) (3 APIs) +11. [plugin/kvm](#module-pluginkvm) (5 APIs) +12. [plugin/ldap](#module-pluginldap) (9 APIs) +13. [plugin/loadBalancer](#module-pluginloadbalancer) (35 APIs) +14. [plugin/localstorage](#module-pluginlocalstorage) (5 APIs) +15. [plugin/nfsPrimaryStorage](#module-pluginnfsprimarystorage) (1 APIs) +16. [plugin/portForwarding](#module-pluginportforwarding) (8 APIs) +17. [plugin/sdnController](#module-pluginsdncontroller) (6 APIs) +18. [plugin/securityGroup](#module-pluginsecuritygroup) (21 APIs) +19. [plugin/sftpBackupStorage](#module-pluginsftpbackupstorage) (4 APIs) +20. [plugin/sharedMountPointPrimaryStorage](#module-pluginsharedmountpointprimarystorage) (1 APIs) +21. [plugin/sshKeyPair](#module-pluginsshkeypair) (7 APIs) +22. [plugin/sugonSdnController](#module-pluginsugonsdncontroller) (1 APIs) +23. [plugin/vip](#module-pluginvip) (7 APIs) +24. [plugin/virtualRouterProvider](#module-pluginvirtualrouterprovider) (10 APIs) +25. [plugin/vxlan](#module-pluginvxlan) (12 APIs) +26. [premium/accesskey](#module-premiumaccesskey) (4 APIs) +27. [premium/autoscaling](#module-premiumautoscaling) (30 APIs) +28. [premium/baremetal](#module-premiumbaremetal) (38 APIs) +29. [premium/baremetal2](#module-premiumbaremetal2) (52 APIs) +30. [premium/billing](#module-premiumbilling) (23 APIs) +31. [premium/cbt](#module-premiumcbt) (7 APIs) +32. [premium/cdp](#module-premiumcdp) (21 APIs) +33. [premium/cloudformation](#module-premiumcloudformation) (19 APIs) +34. [premium/crypto](#module-premiumcrypto) (32 APIs) +35. [premium/drs](#module-premiumdrs) (10 APIs) +36. [premium/externalbackup](#module-premiumexternalbackup) (4 APIs) +37. [premium/faulttolerance](#module-premiumfaulttolerance) (4 APIs) +38. [premium/guesttools](#module-premiumguesttools) (11 APIs) +39. [premium/hybrid](#module-premiumhybrid) (158 APIs) +40. [premium/iam1](#module-premiumiam1) (16 APIs) +41. [premium/iam2](#module-premiumiam2) (87 APIs) +42. [premium/imagereplicator](#module-premiumimagereplicator) (4 APIs) +43. [premium/log](#module-premiumlog) (4 APIs) +44. [premium/loginControl](#module-premiumlogincontrol) (7 APIs) +45. [premium/managements](#module-premiummanagements) (3 APIs) +46. [premium/mevoco](#module-premiummevoco) (309 APIs) +47. [premium/plugin-premium/aliyunproxy](#module-premiumplugin-premiumaliyunproxy) (8 APIs) +48. [premium/plugin-premium/aliyun-storage](#module-premiumplugin-premiumaliyun-storage) (26 APIs) +49. [premium/plugin-premium/block-primary-storage](#module-premiumplugin-premiumblock-primary-storage) (4 APIs) +50. [premium/plugin-premium/daho](#module-premiumplugin-premiumdaho) (14 APIs) +51. [premium/plugin-premium/flowMeter](#module-premiumplugin-premiumflowmeter) (15 APIs) +52. [premium/plugin-premium/ministorage](#module-premiumplugin-premiumministorage) (5 APIs) +53. [premium/plugin-premium/multicast-router](#module-premiumplugin-premiummulticast-router) (7 APIs) +54. [premium/plugin-premium/ovf](#module-premiumplugin-premiumovf) (6 APIs) +55. [premium/plugin-premium/policyRoute](#module-premiumplugin-premiumpolicyroute) (19 APIs) +56. [premium/plugin-premium/portMirror](#module-premiumplugin-premiumportmirror) (10 APIs) +57. [premium/plugin-premium/routeProtocol](#module-premiumplugin-premiumrouteprotocol) (11 APIs) +58. [premium/plugin-premium/sns-aliyun-sms](#module-premiumplugin-premiumsns-aliyun-sms) (2 APIs) +59. [premium/plugin-premium/software-package-plugin](#module-premiumplugin-premiumsoftware-package-plugin) (7 APIs) +60. [premium/plugin-premium/sso-plugin](#module-premiumplugin-premiumsso-plugin) (10 APIs) +61. [premium/plugin-premium/storage-device](#module-premiumplugin-premiumstorage-device) (30 APIs) +62. [premium/plugin-premium/ticket](#module-premiumplugin-premiumticket) (23 APIs) +63. [premium/plugin-premium/virtualSwitchNetwork](#module-premiumplugin-premiumvirtualswitchnetwork) (17 APIs) +64. [premium/plugin-premium/vpcFirewall](#module-premiumplugin-premiumvpcfirewall) (29 APIs) +65. [premium/plugin-premium/zboxbackup](#module-premiumplugin-premiumzboxbackup) (3 APIs) +66. [premium/plugin-premium/zce-x-plugin](#module-premiumplugin-premiumzce-x-plugin) (9 APIs) +67. [premium/plugin-premium/zops-plugin](#module-premiumplugin-premiumzops-plugin) (5 APIs) +68. [premium/plugin-premium/zstone-plugin](#module-premiumplugin-premiumzstone-plugin) (7 APIs) +69. [premium/plugin-premium/zsv](#module-premiumplugin-premiumzsv) (2 APIs) +70. [premium/sharedblock](#module-premiumsharedblock) (8 APIs) +71. [premium/slb](#module-premiumslb) (7 APIs) +72. [premium/snmp](#module-premiumsnmp) (5 APIs) +73. [premium/sns](#module-premiumsns) (70 APIs) +74. [premium/tag2](#module-premiumtag2) (5 APIs) +75. [premium/twoFactorAuthentication](#module-premiumtwofactorauthentication) (4 APIs) +76. [premium/volumebackup](#module-premiumvolumebackup) (27 APIs) +77. [premium/vpc](#module-premiumvpc) (38 APIs) +78. [premium/xdragon](#module-premiumxdragon) (1 APIs) +79. [premium/zbox](#module-premiumzbox) (4 APIs) +80. [premium/zwatch](#module-premiumzwatch) (93 APIs) +81. [resourceconfig](#module-resourceconfig) (7 APIs) +82. [search](#module-search) (3 APIs) +83. [simulator/simulatorHeader](#module-simulatorsimulatorheader) (3 APIs) + +--- + + + +## 模块:`core` + +### `org.zstack.core.captcha` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIRefreshCaptchaMsg` | core/src/main/java/org/zstack/core/captcha/APIRefreshCaptchaMsg.java | + +### `org.zstack.core.config` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetGlobalConfigOptionsMsg` | core/src/main/java/org/zstack/core/config/APIGetGlobalConfigOptionsMsg.java | +| 2 | `APIQueryGlobalConfigMsg` | core/src/main/java/org/zstack/core/config/APIQueryGlobalConfigMsg.java | +| 3 | `APIResetGlobalConfigMsg` | core/src/main/java/org/zstack/core/config/APIResetGlobalConfigMsg.java | +| 4 | `APIUpdateGlobalConfigMsg` | core/src/main/java/org/zstack/core/config/APIUpdateGlobalConfigMsg.java | + +### `org.zstack.core.debug` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICleanQueueMsg` | core/src/main/java/org/zstack/core/debug/APICleanQueueMsg.java | +| 2 | `APIDebugSignalMsg` | core/src/main/java/org/zstack/core/debug/APIDebugSignalMsg.java | +| 3 | `APIGetDebugSignalMsg` | core/src/main/java/org/zstack/core/debug/APIGetDebugSignalMsg.java | + +### `org.zstack.core.errorcode` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICheckElaborationContentMsg` | core/src/main/java/org/zstack/core/errorcode/APICheckElaborationContentMsg.java | +| 2 | `APIGetElaborationCategoriesMsg` | core/src/main/java/org/zstack/core/errorcode/APIGetElaborationCategoriesMsg.java | +| 3 | `APIGetElaborationsMsg` | core/src/main/java/org/zstack/core/errorcode/APIGetElaborationsMsg.java | +| 4 | `APIReloadElaborationMsg` | core/src/main/java/org/zstack/core/errorcode/APIReloadElaborationMsg.java | + +### `org.zstack.core.eventlog` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryEventLogMsg` | core/src/main/java/org/zstack/core/eventlog/APIQueryEventLogMsg.java | + +### `org.zstack.core.gc` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteGCJobMsg` | core/src/main/java/org/zstack/core/gc/APIDeleteGCJobMsg.java | +| 2 | `APIQueryGCJobMsg` | core/src/main/java/org/zstack/core/gc/APIQueryGCJobMsg.java | +| 3 | `APITriggerGCJobMsg` | core/src/main/java/org/zstack/core/gc/APITriggerGCJobMsg.java | + +--- + + + +## 模块:`header` + +### `org.zstack.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIIsOpensourceVersionMsg` | header/src/main/java/org/zstack/header/APIIsOpensourceVersionMsg.java | + +### `org.zstack.header.allocator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetCpuMemoryCapacityMsg` | header/src/main/java/org/zstack/header/allocator/APIGetCpuMemoryCapacityMsg.java | +| 2 | `APIGetHostAllocatorStrategiesMsg` | header/src/main/java/org/zstack/header/allocator/APIGetHostAllocatorStrategiesMsg.java | + +### `org.zstack.header.apimediator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIIsReadyToGoMsg` | header/src/main/java/org/zstack/header/apimediator/APIIsReadyToGoMsg.java | + +### `org.zstack.header.cluster` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeClusterStateMsg` | header/src/main/java/org/zstack/header/cluster/APIChangeClusterStateMsg.java | +| 2 | `APICreateClusterMsg` | header/src/main/java/org/zstack/header/cluster/APICreateClusterMsg.java | +| 3 | `APIDeleteClusterMsg` | header/src/main/java/org/zstack/header/cluster/APIDeleteClusterMsg.java | +| 4 | `APIQueryClusterMsg` | header/src/main/java/org/zstack/header/cluster/APIQueryClusterMsg.java | +| 5 | `APIUpdateClusterMsg` | header/src/main/java/org/zstack/header/cluster/APIUpdateClusterMsg.java | +| 6 | `APIUpdateClusterOSMsg` | header/src/main/java/org/zstack/header/cluster/APIUpdateClusterOSMsg.java | + +### `org.zstack.header.configuration` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeDiskOfferingStateMsg` | header/src/main/java/org/zstack/header/configuration/APIChangeDiskOfferingStateMsg.java | +| 2 | `APIChangeInstanceOfferingStateMsg` | header/src/main/java/org/zstack/header/configuration/APIChangeInstanceOfferingStateMsg.java | +| 3 | `APICreateDiskOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APICreateDiskOfferingMsg.java | +| 4 | `APICreateInstanceOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APICreateInstanceOfferingMsg.java | +| 5 | `APIDeleteDiskOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIDeleteDiskOfferingMsg.java | +| 6 | `APIDeleteInstanceOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIDeleteInstanceOfferingMsg.java | +| 7 | `APIGenerateApiJsonTemplateMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateApiJsonTemplateMsg.java | +| 8 | `APIGenerateApiTypeScriptDefinitionMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateApiTypeScriptDefinitionMsg.java | +| 9 | `APIGenerateGroovyClassMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateGroovyClassMsg.java | +| 10 | `APIGenerateSqlForeignKeyMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateSqlForeignKeyMsg.java | +| 11 | `APIGenerateSqlIndexMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateSqlIndexMsg.java | +| 12 | `APIGenerateSqlVOViewMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateSqlVOViewMsg.java | +| 13 | `APIGenerateTestLinkDocumentMsg` | header/src/main/java/org/zstack/header/configuration/APIGenerateTestLinkDocumentMsg.java | +| 14 | `APIGetGlobalPropertyMsg` | header/src/main/java/org/zstack/header/configuration/APIGetGlobalPropertyMsg.java | +| 15 | `APIQueryDiskOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIQueryDiskOfferingMsg.java | +| 16 | `APIQueryInstanceOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIQueryInstanceOfferingMsg.java | +| 17 | `APIUpdateDiskOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIUpdateDiskOfferingMsg.java | +| 18 | `APIUpdateInstanceOfferingMsg` | header/src/main/java/org/zstack/header/configuration/APIUpdateInstanceOfferingMsg.java | + +### `org.zstack.header.console` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryConsoleProxyAgentMsg` | header/src/main/java/org/zstack/header/console/APIQueryConsoleProxyAgentMsg.java | +| 2 | `APIReconnectConsoleProxyAgentMsg` | header/src/main/java/org/zstack/header/console/APIReconnectConsoleProxyAgentMsg.java | +| 3 | `APIRequestConsoleAccessMsg` | header/src/main/java/org/zstack/header/console/APIRequestConsoleAccessMsg.java | +| 4 | `APIUpdateConsoleProxyAgentMsg` | header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsg.java | + +### `org.zstack.header.core` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetChainTaskMsg` | header/src/main/java/org/zstack/header/core/APIGetChainTaskMsg.java | + +### `org.zstack.header.core.encrypt` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetEncryptedFieldMsg` | header/src/main/java/org/zstack/header/core/encrypt/APIGetEncryptedFieldMsg.java | +| 2 | `APIUpdateEncryptKeyMsg` | header/src/main/java/org/zstack/header/core/encrypt/APIUpdateEncryptKeyMsg.java | + +### `org.zstack.header.core.external.service` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetExternalServicesMsg` | header/src/main/java/org/zstack/header/core/external/service/APIGetExternalServicesMsg.java | +| 2 | `APIReloadExternalServiceMsg` | header/src/main/java/org/zstack/header/core/external/service/APIReloadExternalServiceMsg.java | + +### `org.zstack.header.core.progress` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetTaskProgressMsg` | header/src/main/java/org/zstack/header/core/progress/APIGetTaskProgressMsg.java | + +### `org.zstack.header.core.webhooks` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateWebhookMsg` | header/src/main/java/org/zstack/header/core/webhooks/APICreateWebhookMsg.java | +| 2 | `APIDeleteWebhookMsg` | header/src/main/java/org/zstack/header/core/webhooks/APIDeleteWebhookMsg.java | +| 3 | `APIQueryWebhookMsg` | header/src/main/java/org/zstack/header/core/webhooks/APIQueryWebhookMsg.java | +| 4 | `APIUpdateWebhookMsg` | header/src/main/java/org/zstack/header/core/webhooks/APIUpdateWebhookMsg.java | + +### `org.zstack.header.host` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddHostMsg` | header/src/main/java/org/zstack/header/host/APIAddHostMsg.java | +| 2 | `APIChangeHostStateMsg` | header/src/main/java/org/zstack/header/host/APIChangeHostStateMsg.java | +| 3 | `APIDeleteHostMsg` | header/src/main/java/org/zstack/header/host/APIDeleteHostMsg.java | +| 4 | `APIGetHostBlockDevicesMsg` | header/src/main/java/org/zstack/header/host/APIGetHostBlockDevicesMsg.java | +| 5 | `APIGetHostPowerStatusMsg` | header/src/main/java/org/zstack/header/host/APIGetHostPowerStatusMsg.java | +| 6 | `APIGetHostSensorsMsg` | header/src/main/java/org/zstack/header/host/APIGetHostSensorsMsg.java | +| 7 | `APIGetHostTaskMsg` | header/src/main/java/org/zstack/header/host/APIGetHostTaskMsg.java | +| 8 | `APIGetHostWebSshUrlMsg` | header/src/main/java/org/zstack/header/host/APIGetHostWebSshUrlMsg.java | +| 9 | `APIGetHypervisorTypesMsg` | header/src/main/java/org/zstack/header/host/APIGetHypervisorTypesMsg.java | +| 10 | `APIGetPhysicalMachineBlockDevicesMsg` | header/src/main/java/org/zstack/header/host/APIGetPhysicalMachineBlockDevicesMsg.java | +| 11 | `APIMountBlockDeviceMsg` | header/src/main/java/org/zstack/header/host/APIMountBlockDeviceMsg.java | +| 12 | `APIPowerOnHostMsg` | header/src/main/java/org/zstack/header/host/APIPowerOnHostMsg.java | +| 13 | `APIPowerResetHostMsg` | header/src/main/java/org/zstack/header/host/APIPowerResetHostMsg.java | +| 14 | `APIQueryHostMsg` | header/src/main/java/org/zstack/header/host/APIQueryHostMsg.java | +| 15 | `APIReconnectHostMsg` | header/src/main/java/org/zstack/header/host/APIReconnectHostMsg.java | +| 16 | `APIShutdownHostMsg` | header/src/main/java/org/zstack/header/host/APIShutdownHostMsg.java | +| 17 | `APIUpdateHostIpmiMsg` | header/src/main/java/org/zstack/header/host/APIUpdateHostIpmiMsg.java | +| 18 | `APIUpdateHostMsg` | header/src/main/java/org/zstack/header/host/APIUpdateHostMsg.java | +| 19 | `APIUpdateHostnameMsg` | header/src/main/java/org/zstack/header/host/APIUpdateHostnameMsg.java | +| 20 | `APIUpdateHostNqnMsg` | header/src/main/java/org/zstack/header/host/APIUpdateHostNqnMsg.java | + +### `org.zstack.header.identity` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeResourceOwnerMsg` | header/src/main/java/org/zstack/header/identity/APIChangeResourceOwnerMsg.java | +| 2 | `APICreateAccountMsg` | header/src/main/java/org/zstack/header/identity/APICreateAccountMsg.java | +| 3 | `APIDeleteAccountMsg` | header/src/main/java/org/zstack/header/identity/APIDeleteAccountMsg.java | +| 4 | `APIGetAccountQuotaUsageMsg` | header/src/main/java/org/zstack/header/identity/APIGetAccountQuotaUsageMsg.java | +| 5 | `APIGetResourceAccountMsg` | header/src/main/java/org/zstack/header/identity/APIGetResourceAccountMsg.java | +| 6 | `APILogInByAccountMsg` | header/src/main/java/org/zstack/header/identity/APILogInByAccountMsg.java | +| 7 | `APILogOutMsg` | header/src/main/java/org/zstack/header/identity/APILogOutMsg.java | +| 8 | `APIQueryAccountMsg` | header/src/main/java/org/zstack/header/identity/APIQueryAccountMsg.java | +| 9 | `APIQueryAccountResourceRefMsg` | header/src/main/java/org/zstack/header/identity/APIQueryAccountResourceRefMsg.java | +| 10 | `APIQueryQuotaMsg` | header/src/main/java/org/zstack/header/identity/APIQueryQuotaMsg.java | +| 11 | `APIRenewSessionMsg` | header/src/main/java/org/zstack/header/identity/APIRenewSessionMsg.java | +| 12 | `APIRevokeResourceSharingMsg` | header/src/main/java/org/zstack/header/identity/APIRevokeResourceSharingMsg.java | +| 13 | `APIShareResourceMsg` | header/src/main/java/org/zstack/header/identity/APIShareResourceMsg.java | +| 14 | `APIUpdateAccountMsg` | header/src/main/java/org/zstack/header/identity/APIUpdateAccountMsg.java | +| 15 | `APIUpdateQuotaMsg` | header/src/main/java/org/zstack/header/identity/APIUpdateQuotaMsg.java | +| 16 | `APIValidateSessionMsg` | header/src/main/java/org/zstack/header/identity/APIValidateSessionMsg.java | + +### `org.zstack.header.identity.login` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetLoginProceduresMsg` | header/src/main/java/org/zstack/header/identity/login/APIGetLoginProceduresMsg.java | +| 2 | `APILogInMsg` | header/src/main/java/org/zstack/header/identity/login/APILogInMsg.java | + +### `org.zstack.header.identity.role.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachRoleToAccountMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIAttachRoleToAccountMsg.java | +| 2 | `APICreateRoleMsg` | header/src/main/java/org/zstack/header/identity/role/api/APICreateRoleMsg.java | +| 3 | `APIDeleteRoleMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIDeleteRoleMsg.java | +| 4 | `APIDetachRoleFromAccountMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIDetachRoleFromAccountMsg.java | +| 5 | `APIGetRolePolicyActionsMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIGetRolePolicyActionsMsg.java | +| 6 | `APIQueryRoleAccountRefMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIQueryRoleAccountRefMsg.java | +| 7 | `APIQueryRoleMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIQueryRoleMsg.java | +| 8 | `APIUpdateRoleMsg` | header/src/main/java/org/zstack/header/identity/role/api/APIUpdateRoleMsg.java | + +### `org.zstack.header.image` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddImageMsg` | header/src/main/java/org/zstack/header/image/APIAddImageMsg.java | +| 2 | `APICalculateImageHashMsg` | header/src/main/java/org/zstack/header/image/APICalculateImageHashMsg.java | +| 3 | `APIChangeImageStateMsg` | header/src/main/java/org/zstack/header/image/APIChangeImageStateMsg.java | +| 4 | `APICreateDataVolumeTemplateFromVolumeMsg` | header/src/main/java/org/zstack/header/image/APICreateDataVolumeTemplateFromVolumeMsg.java | +| 5 | `APICreateDataVolumeTemplateFromVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/image/APICreateDataVolumeTemplateFromVolumeSnapshotMsg.java | +| 6 | `APICreateRootVolumeTemplateFromRootVolumeMsg` | header/src/main/java/org/zstack/header/image/APICreateRootVolumeTemplateFromRootVolumeMsg.java | +| 7 | `APICreateRootVolumeTemplateFromVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/image/APICreateRootVolumeTemplateFromVolumeSnapshotMsg.java | +| 8 | `APIDeleteImageMsg` | header/src/main/java/org/zstack/header/image/APIDeleteImageMsg.java | +| 9 | `APIExpungeImageMsg` | header/src/main/java/org/zstack/header/image/APIExpungeImageMsg.java | +| 10 | `APIGetCandidateBackupStorageForCreatingImageMsg` | header/src/main/java/org/zstack/header/image/APIGetCandidateBackupStorageForCreatingImageMsg.java | +| 11 | `APIGetCandidateImagesForCreatingVmMsg` | header/src/main/java/org/zstack/header/image/APIGetCandidateImagesForCreatingVmMsg.java | +| 12 | `APIGetUploadImageJobDetailsMsg` | header/src/main/java/org/zstack/header/image/APIGetUploadImageJobDetailsMsg.java | +| 13 | `APIQueryImageMsg` | header/src/main/java/org/zstack/header/image/APIQueryImageMsg.java | +| 14 | `APIRecoverImageMsg` | header/src/main/java/org/zstack/header/image/APIRecoverImageMsg.java | +| 15 | `APISetImageBootModeMsg` | header/src/main/java/org/zstack/header/image/APISetImageBootModeMsg.java | +| 16 | `APISyncImageSizeMsg` | header/src/main/java/org/zstack/header/image/APISyncImageSizeMsg.java | +| 17 | `APIUpdateImageMsg` | header/src/main/java/org/zstack/header/image/APIUpdateImageMsg.java | + +### `org.zstack.header.longjob` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICancelLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APICancelLongJobMsg.java | +| 2 | `APICleanLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APICleanLongJobMsg.java | +| 3 | `APIDeleteLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APIDeleteLongJobMsg.java | +| 4 | `APIQueryLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APIQueryLongJobMsg.java | +| 5 | `APIRerunLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APIRerunLongJobMsg.java | +| 6 | `APIResumeLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APIResumeLongJobMsg.java | +| 7 | `APISubmitLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APISubmitLongJobMsg.java | +| 8 | `APIUpdateLongJobMsg` | header/src/main/java/org/zstack/header/longjob/APIUpdateLongJobMsg.java | + +### `org.zstack.header.managementnode` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetCurrentTimeMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetCurrentTimeMsg.java | +| 2 | `APIGetManagementNodeArchMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetManagementNodeArchMsg.java | +| 3 | `APIGetManagementNodeOSMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetManagementNodeOSMsg.java | +| 4 | `APIGetPlatformTimeZoneMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetPlatformTimeZoneMsg.java | +| 5 | `APIGetSupportAPIsMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetSupportAPIsMsg.java | +| 6 | `APIGetVersionMsg` | header/src/main/java/org/zstack/header/managementnode/APIGetVersionMsg.java | +| 7 | `APIQueryManagementNodeMsg` | header/src/main/java/org/zstack/header/managementnode/APIQueryManagementNodeMsg.java | + +### `org.zstack.header.network.l2` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachL2NetworkToClusterMsg` | header/src/main/java/org/zstack/header/network/l2/APIAttachL2NetworkToClusterMsg.java | +| 2 | `APIAttachL2NetworkToHostMsg` | header/src/main/java/org/zstack/header/network/l2/APIAttachL2NetworkToHostMsg.java | +| 3 | `APICreateL2NetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APICreateL2NetworkMsg.java | +| 4 | `APICreateL2NoVlanNetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APICreateL2NoVlanNetworkMsg.java | +| 5 | `APICreateL2VlanNetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APICreateL2VlanNetworkMsg.java | +| 6 | `APIDeleteL2NetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APIDeleteL2NetworkMsg.java | +| 7 | `APIDetachL2NetworkFromClusterMsg` | header/src/main/java/org/zstack/header/network/l2/APIDetachL2NetworkFromClusterMsg.java | +| 8 | `APIDetachL2NetworkFromHostMsg` | header/src/main/java/org/zstack/header/network/l2/APIDetachL2NetworkFromHostMsg.java | +| 9 | `APIGetCandidateClustersForAttachingL2NetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APIGetCandidateClustersForAttachingL2NetworkMsg.java | +| 10 | `APIGetCandidateL2NetworksForAttachingClusterMsg` | header/src/main/java/org/zstack/header/network/l2/APIGetCandidateL2NetworksForAttachingClusterMsg.java | +| 11 | `APIGetL2NetworkTypesMsg` | header/src/main/java/org/zstack/header/network/l2/APIGetL2NetworkTypesMsg.java | +| 12 | `APIGetVSwitchTypesMsg` | header/src/main/java/org/zstack/header/network/l2/APIGetVSwitchTypesMsg.java | +| 13 | `APIQueryL2NetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APIQueryL2NetworkMsg.java | +| 14 | `APIQueryL2VlanNetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APIQueryL2VlanNetworkMsg.java | +| 15 | `APIUpdateL2NetworkMsg` | header/src/main/java/org/zstack/header/network/l2/APIUpdateL2NetworkMsg.java | +| 16 | `APIUpdateL2NetworkVirtualNetworkIdMsg` | header/src/main/java/org/zstack/header/network/l2/APIUpdateL2NetworkVirtualNetworkIdMsg.java | + +### `org.zstack.header.network.l3` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddDnsToL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddDnsToL3NetworkMsg.java | +| 2 | `APIAddHostRouteToL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddHostRouteToL3NetworkMsg.java | +| 3 | `APIAddIpRangeByNetworkCidrMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddIpRangeByNetworkCidrMsg.java | +| 4 | `APIAddIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddIpRangeMsg.java | +| 5 | `APIAddIpv6RangeByNetworkCidrMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddIpv6RangeByNetworkCidrMsg.java | +| 6 | `APIAddIpv6RangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddIpv6RangeMsg.java | +| 7 | `APIAddReservedIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIAddReservedIpRangeMsg.java | +| 8 | `APIChangeL3NetworkStateMsg` | header/src/main/java/org/zstack/header/network/l3/APIChangeL3NetworkStateMsg.java | +| 9 | `APICheckIpAvailabilityMsg` | header/src/main/java/org/zstack/header/network/l3/APICheckIpAvailabilityMsg.java | +| 10 | `APICreateL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APICreateL3NetworkMsg.java | +| 11 | `APIDeleteIpAddressMsg` | header/src/main/java/org/zstack/header/network/l3/APIDeleteIpAddressMsg.java | +| 12 | `APIDeleteIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIDeleteIpRangeMsg.java | +| 13 | `APIDeleteL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIDeleteL3NetworkMsg.java | +| 14 | `APIDeleteReservedIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIDeleteReservedIpRangeMsg.java | +| 15 | `APIGetFreeIpMsg` | header/src/main/java/org/zstack/header/network/l3/APIGetFreeIpMsg.java | +| 16 | `APIGetIpAddressCapacityMsg` | header/src/main/java/org/zstack/header/network/l3/APIGetIpAddressCapacityMsg.java | +| 17 | `APIGetL3NetworkMtuMsg` | header/src/main/java/org/zstack/header/network/l3/APIGetL3NetworkMtuMsg.java | +| 18 | `APIGetL3NetworkRouterInterfaceIpMsg` | header/src/main/java/org/zstack/header/network/l3/APIGetL3NetworkRouterInterfaceIpMsg.java | +| 19 | `APIGetL3NetworkTypesMsg` | header/src/main/java/org/zstack/header/network/l3/APIGetL3NetworkTypesMsg.java | +| 20 | `APIQueryAddressPoolMsg` | header/src/main/java/org/zstack/header/network/l3/APIQueryAddressPoolMsg.java | +| 21 | `APIQueryIpAddressMsg` | header/src/main/java/org/zstack/header/network/l3/APIQueryIpAddressMsg.java | +| 22 | `APIQueryIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIQueryIpRangeMsg.java | +| 23 | `APIQueryL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIQueryL3NetworkMsg.java | +| 24 | `APIRemoveDnsFromL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIRemoveDnsFromL3NetworkMsg.java | +| 25 | `APIRemoveHostRouteFromL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIRemoveHostRouteFromL3NetworkMsg.java | +| 26 | `APISetL3NetworkMtuMsg` | header/src/main/java/org/zstack/header/network/l3/APISetL3NetworkMtuMsg.java | +| 27 | `APISetL3NetworkRouterInterfaceIpMsg` | header/src/main/java/org/zstack/header/network/l3/APISetL3NetworkRouterInterfaceIpMsg.java | +| 28 | `APIUpdateIpRangeMsg` | header/src/main/java/org/zstack/header/network/l3/APIUpdateIpRangeMsg.java | +| 29 | `APIUpdateL3NetworkMsg` | header/src/main/java/org/zstack/header/network/l3/APIUpdateL3NetworkMsg.java | + +### `org.zstack.header.network.service` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddNetworkServiceProviderMsg` | header/src/main/java/org/zstack/header/network/service/APIAddNetworkServiceProviderMsg.java | +| 2 | `APIAttachNetworkServiceProviderToL2NetworkMsg` | header/src/main/java/org/zstack/header/network/service/APIAttachNetworkServiceProviderToL2NetworkMsg.java | +| 3 | `APIAttachNetworkServiceToL3NetworkMsg` | header/src/main/java/org/zstack/header/network/service/APIAttachNetworkServiceToL3NetworkMsg.java | +| 4 | `APIDetachNetworkServiceFromL3NetworkMsg` | header/src/main/java/org/zstack/header/network/service/APIDetachNetworkServiceFromL3NetworkMsg.java | +| 5 | `APIDetachNetworkServiceProviderFromL2NetworkMsg` | header/src/main/java/org/zstack/header/network/service/APIDetachNetworkServiceProviderFromL2NetworkMsg.java | +| 6 | `APIGetNetworkServiceTypesMsg` | header/src/main/java/org/zstack/header/network/service/APIGetNetworkServiceTypesMsg.java | +| 7 | `APIQueryNetworkServiceL3NetworkRefMsg` | header/src/main/java/org/zstack/header/network/service/APIQueryNetworkServiceL3NetworkRefMsg.java | +| 8 | `APIQueryNetworkServiceProviderMsg` | header/src/main/java/org/zstack/header/network/service/APIQueryNetworkServiceProviderMsg.java | + +### `org.zstack.header.query` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGenerateInventoryQueryDetailsMsg` | header/src/main/java/org/zstack/header/query/APIGenerateInventoryQueryDetailsMsg.java | +| 2 | `APIGenerateQueryableFieldsMsg` | header/src/main/java/org/zstack/header/query/APIGenerateQueryableFieldsMsg.java | + +### `org.zstack.header.resourceattribute.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateResourceAttributeKeyMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APICreateResourceAttributeKeyMsg.java | +| 2 | `APICreateResourceAttributeValueMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APICreateResourceAttributeValueMsg.java | +| 3 | `APIDeleteResourceAttributeKeyMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APIDeleteResourceAttributeKeyMsg.java | +| 4 | `APIDeleteResourceAttributeValueMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APIDeleteResourceAttributeValueMsg.java | +| 5 | `APIQueryResourceAttributeKeyMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APIQueryResourceAttributeKeyMsg.java | +| 6 | `APIQueryResourceAttributeValueMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APIQueryResourceAttributeValueMsg.java | +| 7 | `APIUpdateResourceAttributeKeyMsg` | header/src/main/java/org/zstack/header/resourceattribute/api/APIUpdateResourceAttributeKeyMsg.java | + +### `org.zstack.header.search` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSearchIndexMsg` | header/src/main/java/org/zstack/header/search/APICreateSearchIndexMsg.java | +| 2 | `APIDeleteSearchIndexMsg` | header/src/main/java/org/zstack/header/search/APIDeleteSearchIndexMsg.java | + +### `org.zstack.header.storage.addon.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddExternalBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/addon/backup/APIAddExternalBackupStorageMsg.java | + +### `org.zstack.header.storage.addon.primary` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddExternalPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/addon/primary/APIAddExternalPrimaryStorageMsg.java | +| 2 | `APIDiscoverExternalPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/addon/primary/APIDiscoverExternalPrimaryStorageMsg.java | +| 3 | `APIUpdateExternalPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/addon/primary/APIUpdateExternalPrimaryStorageMsg.java | + +### `org.zstack.header.storage.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIAddBackupStorageMsg.java | +| 2 | `APIAttachBackupStorageToZoneMsg` | header/src/main/java/org/zstack/header/storage/backup/APIAttachBackupStorageToZoneMsg.java | +| 3 | `APIChangeBackupStorageStateMsg` | header/src/main/java/org/zstack/header/storage/backup/APIChangeBackupStorageStateMsg.java | +| 4 | `APICleanUpTrashOnBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APICleanUpTrashOnBackupStorageMsg.java | +| 5 | `APIDeleteBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIDeleteBackupStorageMsg.java | +| 6 | `APIDeleteExportedImageFromBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIDeleteExportedImageFromBackupStorageMsg.java | +| 7 | `APIDetachBackupStorageFromZoneMsg` | header/src/main/java/org/zstack/header/storage/backup/APIDetachBackupStorageFromZoneMsg.java | +| 8 | `APIExportImageFromBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIExportImageFromBackupStorageMsg.java | +| 9 | `APIGetBackupStorageCapacityMsg` | header/src/main/java/org/zstack/header/storage/backup/APIGetBackupStorageCapacityMsg.java | +| 10 | `APIGetBackupStorageTypesMsg` | header/src/main/java/org/zstack/header/storage/backup/APIGetBackupStorageTypesMsg.java | +| 11 | `APIGetTrashOnBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIGetTrashOnBackupStorageMsg.java | +| 12 | `APIQueryBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIQueryBackupStorageMsg.java | +| 13 | `APIReconnectBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIReconnectBackupStorageMsg.java | +| 14 | `APIScanBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIScanBackupStorageMsg.java | +| 15 | `APIUpdateBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/backup/APIUpdateBackupStorageMsg.java | + +### `org.zstack.header.storage.primary` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIAddPrimaryStorageMsg.java | +| 2 | `APIAddStorageProtocolMsg` | header/src/main/java/org/zstack/header/storage/primary/APIAddStorageProtocolMsg.java | +| 3 | `APIAttachPrimaryStorageToClusterMsg` | header/src/main/java/org/zstack/header/storage/primary/APIAttachPrimaryStorageToClusterMsg.java | +| 4 | `APIChangePrimaryStorageStateMsg` | header/src/main/java/org/zstack/header/storage/primary/APIChangePrimaryStorageStateMsg.java | +| 5 | `APICleanUpImageCacheOnPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APICleanUpImageCacheOnPrimaryStorageMsg.java | +| 6 | `APICleanUpStorageTrashOnPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APICleanUpStorageTrashOnPrimaryStorageMsg.java | +| 7 | `APICleanUpTrashOnPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APICleanUpTrashOnPrimaryStorageMsg.java | +| 8 | `APIDeletePrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIDeletePrimaryStorageMsg.java | +| 9 | `APIDetachPrimaryStorageFromClusterMsg` | header/src/main/java/org/zstack/header/storage/primary/APIDetachPrimaryStorageFromClusterMsg.java | +| 10 | `APIGetPrimaryStorageAllocatorStrategiesMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetPrimaryStorageAllocatorStrategiesMsg.java | +| 11 | `APIGetPrimaryStorageCapacityMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetPrimaryStorageCapacityMsg.java | +| 12 | `APIGetPrimaryStorageLicenseInfoMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetPrimaryStorageLicenseInfoMsg.java | +| 13 | `APIGetPrimaryStorageTypesMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetPrimaryStorageTypesMsg.java | +| 14 | `APIGetPrimaryStorageUsageReportMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetPrimaryStorageUsageReportMsg.java | +| 15 | `APIGetTrashOnPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetTrashOnPrimaryStorageMsg.java | +| 16 | `APIGetVmInstanceMetadataFromPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java | +| 17 | `APIQueryImageCacheMsg` | header/src/main/java/org/zstack/header/storage/primary/APIQueryImageCacheMsg.java | +| 18 | `APIQueryPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIQueryPrimaryStorageMsg.java | +| 19 | `APIReconnectPrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIReconnectPrimaryStorageMsg.java | +| 20 | `APIRegisterVmInstanceMsg` | header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java | +| 21 | `APISyncPrimaryStorageCapacityMsg` | header/src/main/java/org/zstack/header/storage/primary/APISyncPrimaryStorageCapacityMsg.java | +| 22 | `APIUpdatePrimaryStorageMsg` | header/src/main/java/org/zstack/header/storage/primary/APIUpdatePrimaryStorageMsg.java | + +### `org.zstack.header.storage.snapshot` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBackupVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIBackupVolumeSnapshotMsg.java | +| 2 | `APIBatchDeleteVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIBatchDeleteVolumeSnapshotMsg.java | +| 3 | `APIDeleteVolumeSnapshotFromBackupStorageMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotFromBackupStorageMsg.java | +| 4 | `APIDeleteVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java | +| 5 | `APIGetVolumeSnapshotSizeMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIGetVolumeSnapshotSizeMsg.java | +| 6 | `APIQueryVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIQueryVolumeSnapshotMsg.java | +| 7 | `APIQueryVolumeSnapshotTreeMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIQueryVolumeSnapshotTreeMsg.java | +| 8 | `APIRevertVolumeFromSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java | +| 9 | `APIShrinkVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java | +| 10 | `APIUpdateVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java | + +### `org.zstack.header.storage.snapshot.group` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICheckMemorySnapshotGroupConflictMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APICheckMemorySnapshotGroupConflictMsg.java | +| 2 | `APICheckVolumeSnapshotGroupAvailabilityMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APICheckVolumeSnapshotGroupAvailabilityMsg.java | +| 3 | `APIDeleteVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java | +| 4 | `APIQueryVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APIQueryVolumeSnapshotGroupMsg.java | +| 5 | `APIRevertVmFromSnapshotGroupMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java | +| 6 | `APIUngroupVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java | +| 7 | `APIUpdateVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java | + +### `org.zstack.header.tag` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAbstractCreateTagMsg` | header/src/main/java/org/zstack/header/tag/APIAbstractCreateTagMsg.java | +| 2 | `APICreateSystemTagMsg` | header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java | +| 3 | `APICreateSystemTagsMsg` | header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java | +| 4 | `APIDeleteTagMsg` | header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java | +| 5 | `APIQuerySystemTagMsg` | header/src/main/java/org/zstack/header/tag/APIQuerySystemTagMsg.java | +| 6 | `APIQueryUserTagMsg` | header/src/main/java/org/zstack/header/tag/APIQueryUserTagMsg.java | +| 7 | `APIUpdateSystemTagMsg` | header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java | + +### `org.zstack.header.vm` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachIsoToVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java | +| 2 | `APIAttachL3NetworkToVmMsg` | header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java | +| 3 | `APIAttachL3NetworkToVmNicMsg` | header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmNicMsg.java | +| 4 | `APIAttachVmNicToVmMsg` | header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java | +| 5 | `APIChangeInstanceOfferingMsg` | header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java | +| 6 | `APIChangeVmNicNetworkMsg` | header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java | +| 7 | `APIChangeVmNicStateMsg` | header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java | +| 8 | `APIConvertTemplatedVmInstanceToVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java | +| 9 | `APIConvertVmInstanceToTemplatedVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java | +| 10 | `APICreateVmInstanceFromVolumeMsg` | header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeMsg.java | +| 11 | `APICreateVmInstanceFromVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java | +| 12 | `APICreateVmInstanceFromVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotMsg.java | +| 13 | `APICreateVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java | +| 14 | `APICreateVmNicMsg` | header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java | +| 15 | `APIDeleteTemplatedVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteTemplatedVmInstanceMsg.java | +| 16 | `APIDeleteVmBootModeMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java | +| 17 | `APIDeleteVmConsolePasswordMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java | +| 18 | `APIDeleteVmHostnameMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java | +| 19 | `APIDeleteVmNicMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java | +| 20 | `APIDeleteVmSshKeyMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java | +| 21 | `APIDeleteVmStaticIpMsg` | header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java | +| 22 | `APIDestroyVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java | +| 23 | `APIDetachIsoFromVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java | +| 24 | `APIDetachL3NetworkFromVmMsg` | header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java | +| 25 | `APIExpungeVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java | +| 26 | `APIFlattenVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIFlattenVmInstanceMsg.java | +| 27 | `APIFstrimVmMsg` | header/src/main/java/org/zstack/header/vm/APIFstrimVmMsg.java | +| 28 | `APIGetCandidateIsoForAttachingVmMsg` | header/src/main/java/org/zstack/header/vm/APIGetCandidateIsoForAttachingVmMsg.java | +| 29 | `APIGetCandidateL3NetworksForChangeVmNicNetworkMsg` | header/src/main/java/org/zstack/header/vm/APIGetCandidateL3NetworksForChangeVmNicNetworkMsg.java | +| 30 | `APIGetCandidatePrimaryStoragesForCreatingVmMsg` | header/src/main/java/org/zstack/header/vm/APIGetCandidatePrimaryStoragesForCreatingVmMsg.java | +| 31 | `APIGetCandidateVmForAttachingIsoMsg` | header/src/main/java/org/zstack/header/vm/APIGetCandidateVmForAttachingIsoMsg.java | +| 32 | `APIGetCandidateZonesClustersHostsForCreatingVmMsg` | header/src/main/java/org/zstack/header/vm/APIGetCandidateZonesClustersHostsForCreatingVmMsg.java | +| 33 | `APIGetInterdependentL3NetworksBackupStoragesMsg` | header/src/main/java/org/zstack/header/vm/APIGetInterdependentL3NetworksBackupStoragesMsg.java | +| 34 | `APIGetInterdependentL3NetworksImagesMsg` | header/src/main/java/org/zstack/header/vm/APIGetInterdependentL3NetworksImagesMsg.java | +| 35 | `APIGetMemorySnapshotGroupReferenceMsg` | header/src/main/java/org/zstack/header/vm/APIGetMemorySnapshotGroupReferenceMsg.java | +| 36 | `APIGetSpiceCertificatesMsg` | header/src/main/java/org/zstack/header/vm/APIGetSpiceCertificatesMsg.java | +| 37 | `APIGetVmAttachableDataVolumeMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmAttachableDataVolumeMsg.java | +| 38 | `APIGetVmAttachableL3NetworkMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmAttachableL3NetworkMsg.java | +| 39 | `APIGetVmBootOrderMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmBootOrderMsg.java | +| 40 | `APIGetVmCapabilitiesMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmCapabilitiesMsg.java | +| 41 | `APIGetVmConsoleAddressMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmConsoleAddressMsg.java | +| 42 | `APIGetVmConsolePasswordMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmConsolePasswordMsg.java | +| 43 | `APIGetVmDeviceAddressMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmDeviceAddressMsg.java | +| 44 | `APIGetVmDnsMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmDnsMsg.java | +| 45 | `APIGetVmHostnameMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmHostnameMsg.java | +| 46 | `APIGetVmMigrationCandidateHostsMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmMigrationCandidateHostsMsg.java | +| 47 | `APIGetVmNicAttachedNetworkServiceMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmNicAttachedNetworkServiceMsg.java | +| 48 | `APIGetVmsCapabilitiesMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmsCapabilitiesMsg.java | +| 49 | `APIGetVmSshKeyMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmSshKeyMsg.java | +| 50 | `APIGetVmStartingCandidateClustersHostsMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmStartingCandidateClustersHostsMsg.java | +| 51 | `APIGetVmTaskMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmTaskMsg.java | +| 52 | `APIGetVmUptimeMsg` | header/src/main/java/org/zstack/header/vm/APIGetVmUptimeMsg.java | +| 53 | `APIMigrateVmMsg` | header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java | +| 54 | `APIPauseVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIPauseVmInstanceMsg.java | +| 55 | `APIQueryTemplatedVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIQueryTemplatedVmInstanceMsg.java | +| 56 | `APIQueryVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIQueryVmInstanceMsg.java | +| 57 | `APIQueryVmNicMsg` | header/src/main/java/org/zstack/header/vm/APIQueryVmNicMsg.java | +| 58 | `APIQueryVmPriorityConfigMsg` | header/src/main/java/org/zstack/header/vm/APIQueryVmPriorityConfigMsg.java | +| 59 | `APIRebootVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIRebootVmInstanceMsg.java | +| 60 | `APIRecoverVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java | +| 61 | `APIReimageVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java | +| 62 | `APIResumeVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIResumeVmInstanceMsg.java | +| 63 | `APISetVmBootModeMsg` | header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java | +| 64 | `APISetVmBootOrderMsg` | header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java | +| 65 | `APISetVmBootVolumeMsg` | header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java | +| 66 | `APISetVmClockTrackMsg` | header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java | +| 67 | `APISetVmConsolePasswordMsg` | header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java | +| 68 | `APISetVmDnsMsg` | header/src/main/java/org/zstack/header/vm/APISetVmDnsMsg.java | +| 69 | `APISetVmHostnameMsg` | header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java | +| 70 | `APISetVmQxlMemoryMsg` | header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java | +| 71 | `APISetVmSoundTypeMsg` | header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java | +| 72 | `APISetVmSshKeyMsg` | header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java | +| 73 | `APISetVmStaticIpMsg` | header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java | +| 74 | `APIStartVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIStartVmInstanceMsg.java | +| 75 | `APIStopVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIStopVmInstanceMsg.java | +| 76 | `APITakeVmConsoleScreenshotMsg` | header/src/main/java/org/zstack/header/vm/APITakeVmConsoleScreenshotMsg.java | +| 77 | `APIUpdatePriorityConfigMsg` | header/src/main/java/org/zstack/header/vm/APIUpdatePriorityConfigMsg.java | +| 78 | `APIUpdateTemplatedVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIUpdateTemplatedVmInstanceMsg.java | +| 79 | `APIUpdateVmInstanceMsg` | header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java | +| 80 | `APIUpdateVmNicDriverMsg` | header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java | +| 81 | `APIUpdateVmPriorityMsg` | header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java | + +### `org.zstack.header.vm.cdrom` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVmCdRomMsg` | header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java | +| 2 | `APIDeleteVmCdRomMsg` | header/src/main/java/org/zstack/header/vm/cdrom/APIDeleteVmCdRomMsg.java | +| 3 | `APIQueryVmCdRomMsg` | header/src/main/java/org/zstack/header/vm/cdrom/APIQueryVmCdRomMsg.java | +| 4 | `APISetVmInstanceDefaultCdRomMsg` | header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java | +| 5 | `APIUpdateVmCdRomMsg` | header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java | + +### `org.zstack.header.vm.devices` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryVmInstanceResourceMetadataArchiveMsg` | header/src/main/java/org/zstack/header/vm/devices/APIQueryVmInstanceResourceMetadataArchiveMsg.java | +| 2 | `APIQueryVmInstanceResourceMetadataGroupMsg` | header/src/main/java/org/zstack/header/vm/devices/APIQueryVmInstanceResourceMetadataGroupMsg.java | + +### `org.zstack.header.vo` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetResourceNamesMsg` | header/src/main/java/org/zstack/header/vo/APIGetResourceNamesMsg.java | + +### `org.zstack.header.volume` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachDataVolumeToHostMsg` | header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToHostMsg.java | +| 2 | `APIAttachDataVolumeToVmMsg` | header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java | +| 3 | `APIBackupDataVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIBackupDataVolumeMsg.java | +| 4 | `APIBatchSyncVolumeSizeMsg` | header/src/main/java/org/zstack/header/volume/APIBatchSyncVolumeSizeMsg.java | +| 5 | `APIChangeVolumeStateMsg` | header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java | +| 6 | `APICreateDataVolumeFromVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/volume/APICreateDataVolumeFromVolumeSnapshotMsg.java | +| 7 | `APICreateDataVolumeFromVolumeTemplateMsg` | header/src/main/java/org/zstack/header/volume/APICreateDataVolumeFromVolumeTemplateMsg.java | +| 8 | `APICreateDataVolumeMsg` | header/src/main/java/org/zstack/header/volume/APICreateDataVolumeMsg.java | +| 9 | `APICreateVolumeSnapshotGroupMsg` | header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java | +| 10 | `APICreateVolumeSnapshotMsg` | header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java | +| 11 | `APIDeleteDataVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java | +| 12 | `APIDetachDataVolumeFromHostMsg` | header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromHostMsg.java | +| 13 | `APIDetachDataVolumeFromVmMsg` | header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java | +| 14 | `APIExpungeDataVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java | +| 15 | `APIFlattenVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java | +| 16 | `APIGetDataVolumeAttachableVmMsg` | header/src/main/java/org/zstack/header/volume/APIGetDataVolumeAttachableVmMsg.java | +| 17 | `APIGetVolumeCapabilitiesMsg` | header/src/main/java/org/zstack/header/volume/APIGetVolumeCapabilitiesMsg.java | +| 18 | `APIGetVolumeFormatMsg` | header/src/main/java/org/zstack/header/volume/APIGetVolumeFormatMsg.java | +| 19 | `APIQueryVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIQueryVolumeMsg.java | +| 20 | `APIRecoverDataVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java | +| 21 | `APISyncVolumeSizeMsg` | header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java | +| 22 | `APIUndoSnapshotCreationMsg` | header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java | +| 23 | `APIUpdateVolumeMsg` | header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java | + +### `org.zstack.header.zone` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeZoneStateMsg` | header/src/main/java/org/zstack/header/zone/APIChangeZoneStateMsg.java | +| 2 | `APICreateZoneMsg` | header/src/main/java/org/zstack/header/zone/APICreateZoneMsg.java | +| 3 | `APIDeleteZoneMsg` | header/src/main/java/org/zstack/header/zone/APIDeleteZoneMsg.java | +| 4 | `APIGetZoneMsg` | header/src/main/java/org/zstack/header/zone/APIGetZoneMsg.java | +| 5 | `APIQueryZoneMsg` | header/src/main/java/org/zstack/header/zone/APIQueryZoneMsg.java | +| 6 | `APIUpdateZoneMsg` | header/src/main/java/org/zstack/header/zone/APIUpdateZoneMsg.java | + +--- + + + +## 模块:`plugin/account-import` + +### `org.zstack.identity.imports.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryThirdPartyAccountSourceBindingMsg` | plugin/account-import/src/main/java/org/zstack/identity/imports/api/APIQueryThirdPartyAccountSourceBindingMsg.java | + +--- + + + +## 模块:`plugin/acl` + +### `org.zstack.header.acl` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAccessControlListEntryMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListEntryMsg.java | +| 2 | `APIAddAccessControlListRedirectRuleMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIAddAccessControlListRedirectRuleMsg.java | +| 3 | `APIChangeAccessControlListRedirectRuleMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIChangeAccessControlListRedirectRuleMsg.java | +| 4 | `APICreateAccessControlListMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APICreateAccessControlListMsg.java | +| 5 | `APIDeleteAccessControlListMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIDeleteAccessControlListMsg.java | +| 6 | `APIQueryAccessControlListMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIQueryAccessControlListMsg.java | +| 7 | `APIRemoveAccessControlListEntryMsg` | plugin/acl/src/main/java/org/zstack/header/acl/APIRemoveAccessControlListEntryMsg.java | + +--- + + + +## 模块:`plugin/applianceVm` + +### `org.zstack.appliancevm` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryApplianceVmMsg` | plugin/applianceVm/src/main/java/org/zstack/appliancevm/APIQueryApplianceVmMsg.java | + +--- + + + +## 模块:`plugin/ceph` + +### `org.zstack.storage.ceph.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddCephBackupStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/APIAddCephBackupStorageMsg.java | +| 2 | `APIAddMonToCephBackupStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/APIAddMonToCephBackupStorageMsg.java | +| 3 | `APIQueryCephBackupStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/APIQueryCephBackupStorageMsg.java | +| 4 | `APIRemoveMonFromCephBackupStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/APIRemoveMonFromCephBackupStorageMsg.java | +| 5 | `APIUpdateCephBackupStorageMonMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/backup/APIUpdateCephBackupStorageMonMsg.java | + +### `org.zstack.storage.ceph.primary` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddCephPrimaryStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStorageMsg.java | +| 2 | `APIAddCephPrimaryStoragePoolMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddCephPrimaryStoragePoolMsg.java | +| 3 | `APIAddMonToCephPrimaryStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIAddMonToCephPrimaryStorageMsg.java | +| 4 | `APIDeleteCephPrimaryStoragePoolMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIDeleteCephPrimaryStoragePoolMsg.java | +| 5 | `APIQueryCephOsdGroupMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIQueryCephOsdGroupMsg.java | +| 6 | `APIQueryCephPrimaryStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIQueryCephPrimaryStorageMsg.java | +| 7 | `APIQueryCephPrimaryStoragePoolMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIQueryCephPrimaryStoragePoolMsg.java | +| 8 | `APIRemoveMonFromCephPrimaryStorageMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIRemoveMonFromCephPrimaryStorageMsg.java | +| 9 | `APIUpdateCephPrimaryStorageMonMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIUpdateCephPrimaryStorageMonMsg.java | +| 10 | `APIUpdateCephPrimaryStoragePoolMsg` | plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/APIUpdateCephPrimaryStoragePoolMsg.java | + +--- + + + +## 模块:`plugin/directory` + +### `org.zstack.directory` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddResourcesToDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIAddResourcesToDirectoryMsg.java | +| 2 | `APICreateDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APICreateDirectoryMsg.java | +| 3 | `APIDeleteDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIDeleteDirectoryMsg.java | +| 4 | `APIMoveDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIMoveDirectoryMsg.java | +| 5 | `APIMoveResourcesToDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIMoveResourcesToDirectoryMsg.java | +| 6 | `APIQueryDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIQueryDirectoryMsg.java | +| 7 | `APIRemoveResourcesFromDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIRemoveResourcesFromDirectoryMsg.java | +| 8 | `APIUpdateDirectoryMsg` | plugin/directory/src/main/java/org/zstack/directory/APIUpdateDirectoryMsg.java | + +--- + + + +## 模块:`plugin/eip` + +### `org.zstack.network.service.eip` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIAttachEipMsg.java | +| 2 | `APIChangeEipStateMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIChangeEipStateMsg.java | +| 3 | `APICreateEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APICreateEipMsg.java | +| 4 | `APIDeleteEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIDeleteEipMsg.java | +| 5 | `APIDetachEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIDetachEipMsg.java | +| 6 | `APIGetEipAttachableVmNicsMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIGetEipAttachableVmNicsMsg.java | +| 7 | `APIGetVmNicAttachableEipsMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIGetVmNicAttachableEipsMsg.java | +| 8 | `APIQueryEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIQueryEipMsg.java | +| 9 | `APIUpdateEipMsg` | plugin/eip/src/main/java/org/zstack/network/service/eip/APIUpdateEipMsg.java | + +--- + + + +## 模块:`plugin/flatNetworkProvider` + +### `org.zstack.network.service.flat` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeL3NetworkDhcpIpAddressMsg` | plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/APIChangeL3NetworkDhcpIpAddressMsg.java | +| 2 | `APIGetL3NetworkDhcpIpAddressMsg` | plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/APIGetL3NetworkDhcpIpAddressMsg.java | +| 3 | `APIGetL3NetworkIpStatisticMsg` | plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/APIGetL3NetworkIpStatisticMsg.java | + +--- + + + +## 模块:`plugin/hostNetworkInterface` + +### `org.zstack.network.hostNetworkInterface.lldp.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeHostNetworkInterfaceLldpModeMsg` | plugin/hostNetworkInterface/src/main/java/org/zstack/network/hostNetworkInterface/lldp/api/APIChangeHostNetworkInterfaceLldpModeMsg.java | +| 2 | `APIGetHostNetworkInterfaceLldpMsg` | plugin/hostNetworkInterface/src/main/java/org/zstack/network/hostNetworkInterface/lldp/api/APIGetHostNetworkInterfaceLldpMsg.java | +| 3 | `APIQueryHostNetworkInterfaceLldpMsg` | plugin/hostNetworkInterface/src/main/java/org/zstack/network/hostNetworkInterface/lldp/api/APIQueryHostNetworkInterfaceLldpMsg.java | + +--- + + + +## 模块:`plugin/kvm` + +### `org.zstack.kvm` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddKVMHostMsg` | plugin/kvm/src/main/java/org/zstack/kvm/APIAddKVMHostMsg.java | +| 2 | `APIKvmRunShellMsg` | plugin/kvm/src/main/java/org/zstack/kvm/APIKvmRunShellMsg.java | +| 3 | `APIUpdateKVMHostMsg` | plugin/kvm/src/main/java/org/zstack/kvm/APIUpdateKVMHostMsg.java | + +### `org.zstack.kvm.hypervisor.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryHostOsCategoryMsg` | plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/message/APIQueryHostOsCategoryMsg.java | +| 2 | `APIQueryKvmHypervisorInfoMsg` | plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/message/APIQueryKvmHypervisorInfoMsg.java | + +--- + + + +## 模块:`plugin/ldap` + +### `org.zstack.ldap.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddLdapServerMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIAddLdapServerMsg.java | +| 2 | `APICreateLdapBindingMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APICreateLdapBindingMsg.java | +| 3 | `APIDeleteLdapBindingMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIDeleteLdapBindingMsg.java | +| 4 | `APIDeleteLdapServerMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIDeleteLdapServerMsg.java | +| 5 | `APIGetCandidateLdapEntryForBindingMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIGetCandidateLdapEntryForBindingMsg.java | +| 6 | `APIGetLdapEntryMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIGetLdapEntryMsg.java | +| 7 | `APIQueryLdapServerMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIQueryLdapServerMsg.java | +| 8 | `APISyncAccountsFromLdapServerMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APISyncAccountsFromLdapServerMsg.java | +| 9 | `APIUpdateLdapServerMsg` | plugin/ldap/src/main/java/org/zstack/ldap/api/APIUpdateLdapServerMsg.java | + +--- + + + +## 模块:`plugin/loadBalancer` + +### `org.zstack.network.service.lb` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAccessControlListToLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIAddAccessControlListToLoadBalancerMsg.java | +| 2 | `APIAddBackendServerToServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIAddBackendServerToServerGroupMsg.java | +| 3 | `APIAddCertificateToLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIAddCertificateToLoadBalancerListenerMsg.java | +| 4 | `APIAddServerGroupToLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIAddServerGroupToLoadBalancerListenerMsg.java | +| 5 | `APIAddVmNicToLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIAddVmNicToLoadBalancerMsg.java | +| 6 | `APIChangeAccessControlListServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeAccessControlListServerGroupMsg.java | +| 7 | `APIChangeLoadBalancerBackendServerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerBackendServerMsg.java | +| 8 | `APIChangeLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsg.java | +| 9 | `APICreateCertificateMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateCertificateMsg.java | +| 10 | `APICreateLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsg.java | +| 11 | `APICreateLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerMsg.java | +| 12 | `APICreateLoadBalancerServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerServerGroupMsg.java | +| 13 | `APIDeleteCertificateMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIDeleteCertificateMsg.java | +| 14 | `APIDeleteLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIDeleteLoadBalancerListenerMsg.java | +| 15 | `APIDeleteLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIDeleteLoadBalancerMsg.java | +| 16 | `APIDeleteLoadBalancerServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIDeleteLoadBalancerServerGroupMsg.java | +| 17 | `APIGetCandidateL3NetworksForLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIGetCandidateL3NetworksForLoadBalancerMsg.java | +| 18 | `APIGetCandidateL3NetworksForServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIGetCandidateL3NetworksForServerGroupMsg.java | +| 19 | `APIGetCandidateVmNicsForLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIGetCandidateVmNicsForLoadBalancerMsg.java | +| 20 | `APIGetCandidateVmNicsForLoadBalancerServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIGetCandidateVmNicsForLoadBalancerServerGroupMsg.java | +| 21 | `APIGetLoadBalancerListenerACLEntriesMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIGetLoadBalancerListenerACLEntriesMsg.java | +| 22 | `APIQueryCertificateMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIQueryCertificateMsg.java | +| 23 | `APIQueryLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIQueryLoadBalancerListenerMsg.java | +| 24 | `APIQueryLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIQueryLoadBalancerMsg.java | +| 25 | `APIQueryLoadBalancerServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIQueryLoadBalancerServerGroupMsg.java | +| 26 | `APIRefreshLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRefreshLoadBalancerMsg.java | +| 27 | `APIRemoveAccessControlListFromLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRemoveAccessControlListFromLoadBalancerMsg.java | +| 28 | `APIRemoveBackendServerFromServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRemoveBackendServerFromServerGroupMsg.java | +| 29 | `APIRemoveCertificateFromLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRemoveCertificateFromLoadBalancerListenerMsg.java | +| 30 | `APIRemoveServerGroupFromLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRemoveServerGroupFromLoadBalancerListenerMsg.java | +| 31 | `APIRemoveVmNicFromLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIRemoveVmNicFromLoadBalancerMsg.java | +| 32 | `APIUpdateCertificateMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIUpdateCertificateMsg.java | +| 33 | `APIUpdateLoadBalancerListenerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIUpdateLoadBalancerListenerMsg.java | +| 34 | `APIUpdateLoadBalancerMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIUpdateLoadBalancerMsg.java | +| 35 | `APIUpdateLoadBalancerServerGroupMsg` | plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIUpdateLoadBalancerServerGroupMsg.java | + +--- + + + +## 模块:`plugin/localstorage` + +### `org.zstack.storage.primary.local` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddLocalPrimaryStorageMsg` | plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APIAddLocalPrimaryStorageMsg.java | +| 2 | `APIGetLocalStorageHostDiskCapacityMsg` | plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APIGetLocalStorageHostDiskCapacityMsg.java | +| 3 | `APILocalStorageGetVolumeMigratableHostsMsg` | plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageGetVolumeMigratableHostsMsg.java | +| 4 | `APILocalStorageMigrateVolumeMsg` | plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java | +| 5 | `APIQueryLocalStorageResourceRefMsg` | plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APIQueryLocalStorageResourceRefMsg.java | + +--- + + + +## 模块:`plugin/nfsPrimaryStorage` + +### `org.zstack.storage.primary.nfs` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddNfsPrimaryStorageMsg` | plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/APIAddNfsPrimaryStorageMsg.java | + +--- + + + +## 模块:`plugin/portForwarding` + +### `org.zstack.network.service.portforwarding` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachPortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIAttachPortForwardingRuleMsg.java | +| 2 | `APIChangePortForwardingRuleStateMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIChangePortForwardingRuleStateMsg.java | +| 3 | `APICreatePortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APICreatePortForwardingRuleMsg.java | +| 4 | `APIDeletePortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIDeletePortForwardingRuleMsg.java | +| 5 | `APIDetachPortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIDetachPortForwardingRuleMsg.java | +| 6 | `APIGetPortForwardingAttachableVmNicsMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIGetPortForwardingAttachableVmNicsMsg.java | +| 7 | `APIQueryPortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIQueryPortForwardingRuleMsg.java | +| 8 | `APIUpdatePortForwardingRuleMsg` | plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/APIUpdatePortForwardingRuleMsg.java | + +--- + + + +## 模块:`plugin/sdnController` + +### `org.zstack.sdnController.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSdnControllerMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIAddSdnControllerMsg.java | +| 2 | `APICreateL2HardwareVxlanNetworkMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APICreateL2HardwareVxlanNetworkMsg.java | +| 3 | `APICreateL2HardwareVxlanNetworkPoolMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APICreateL2HardwareVxlanNetworkPoolMsg.java | +| 4 | `APIQuerySdnControllerMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIQuerySdnControllerMsg.java | +| 5 | `APIRemoveSdnControllerMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIRemoveSdnControllerMsg.java | +| 6 | `APIUpdateSdnControllerMsg` | plugin/sdnController/src/main/java/org/zstack/sdnController/header/APIUpdateSdnControllerMsg.java | + +--- + + + +## 模块:`plugin/securityGroup` + +### `org.zstack.network.securitygroup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSecurityGroupRuleMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIAddSecurityGroupRuleMsg.java | +| 2 | `APIAddVmNicToSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIAddVmNicToSecurityGroupMsg.java | +| 3 | `APIAttachSecurityGroupToL3NetworkMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIAttachSecurityGroupToL3NetworkMsg.java | +| 4 | `APIChangeSecurityGroupRuleMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIChangeSecurityGroupRuleMsg.java | +| 5 | `APIChangeSecurityGroupRuleStateMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIChangeSecurityGroupRuleStateMsg.java | +| 6 | `APIChangeSecurityGroupStateMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIChangeSecurityGroupStateMsg.java | +| 7 | `APIChangeVmNicSecurityPolicyMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIChangeVmNicSecurityPolicyMsg.java | +| 8 | `APICreateSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APICreateSecurityGroupMsg.java | +| 9 | `APIDeleteSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIDeleteSecurityGroupMsg.java | +| 10 | `APIDeleteSecurityGroupRuleMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIDeleteSecurityGroupRuleMsg.java | +| 11 | `APIDeleteVmNicFromSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIDeleteVmNicFromSecurityGroupMsg.java | +| 12 | `APIDetachSecurityGroupFromL3NetworkMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIDetachSecurityGroupFromL3NetworkMsg.java | +| 13 | `APIGetCandidateVmNicForSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIGetCandidateVmNicForSecurityGroupMsg.java | +| 14 | `APIQuerySecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIQuerySecurityGroupMsg.java | +| 15 | `APIQuerySecurityGroupRuleMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIQuerySecurityGroupRuleMsg.java | +| 16 | `APIQueryVmNicInSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIQueryVmNicInSecurityGroupMsg.java | +| 17 | `APIQueryVmNicSecurityPolicyMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIQueryVmNicSecurityPolicyMsg.java | +| 18 | `APISetVmNicSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APISetVmNicSecurityGroupMsg.java | +| 19 | `APIUpdateSecurityGroupMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIUpdateSecurityGroupMsg.java | +| 20 | `APIUpdateSecurityGroupRulePriorityMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIUpdateSecurityGroupRulePriorityMsg.java | +| 21 | `APIValidateSecurityGroupRuleMsg` | plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/APIValidateSecurityGroupRuleMsg.java | + +--- + + + +## 模块:`plugin/sftpBackupStorage` + +### `org.zstack.storage.backup.sftp` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSftpBackupStorageMsg` | plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/APIAddSftpBackupStorageMsg.java | +| 2 | `APIQuerySftpBackupStorageMsg` | plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/APIQuerySftpBackupStorageMsg.java | +| 3 | `APIReconnectSftpBackupStorageMsg` | plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/APIReconnectSftpBackupStorageMsg.java | +| 4 | `APIUpdateSftpBackupStorageMsg` | plugin/sftpBackupStorage/src/main/java/org/zstack/storage/backup/sftp/APIUpdateSftpBackupStorageMsg.java | + +--- + + + +## 模块:`plugin/sharedMountPointPrimaryStorage` + +### `org.zstack.storage.primary.smp` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSharedMountPointPrimaryStorageMsg` | plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/APIAddSharedMountPointPrimaryStorageMsg.java | + +--- + + + +## 模块:`plugin/sshKeyPair` + +### `org.zstack.header.sshkeypair` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachSshKeyPairToVmInstanceMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIAttachSshKeyPairToVmInstanceMsg.java | +| 2 | `APICreateSshKeyPairMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APICreateSshKeyPairMsg.java | +| 3 | `APIDeleteSshKeyPairMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIDeleteSshKeyPairMsg.java | +| 4 | `APIDetachSshKeyPairFromVmInstanceMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIDetachSshKeyPairFromVmInstanceMsg.java | +| 5 | `APIGenerateSshKeyPairMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIGenerateSshKeyPairMsg.java | +| 6 | `APIQuerySshKeyPairMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIQuerySshKeyPairMsg.java | +| 7 | `APIUpdateSshKeyPairMsg` | plugin/sshKeyPair/src/main/java/org/zstack/header/sshkeypair/APIUpdateSshKeyPairMsg.java | + +--- + + + +## 模块:`plugin/sugonSdnController` + +### `org.zstack.sugonSdnController.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateL2TfNetworkMsg` | plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/header/APICreateL2TfNetworkMsg.java | + +--- + + + +## 模块:`plugin/vip` + +### `org.zstack.network.service.vip` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeVipStateMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APIChangeVipStateMsg.java | +| 2 | `APICheckVipPortAvailabilityMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APICheckVipPortAvailabilityMsg.java | +| 3 | `APICreateVipMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APICreateVipMsg.java | +| 4 | `APIDeleteVipMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APIDeleteVipMsg.java | +| 5 | `APIGetVipAvailablePortMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APIGetVipAvailablePortMsg.java | +| 6 | `APIQueryVipMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APIQueryVipMsg.java | +| 7 | `APIUpdateVipMsg` | plugin/vip/src/main/java/org/zstack/network/service/vip/APIUpdateVipMsg.java | + +--- + + + +## 模块:`plugin/virtualRouterProvider` + +### `org.zstack.network.service.virtualrouter` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVirtualRouterOfferingMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APICreateVirtualRouterOfferingMsg.java | +| 2 | `APICreateVirtualRouterVmMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APICreateVirtualRouterVmMsg.java | +| 3 | `APIGetAttachablePublicL3ForVRouterMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIGetAttachablePublicL3ForVRouterMsg.java | +| 4 | `APIGetVipUsedPortsMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIGetVipUsedPortsMsg.java | +| 5 | `APIProvisionVirtualRouterConfigMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIProvisionVirtualRouterConfigMsg.java | +| 6 | `APIQueryVirtualRouterOfferingMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIQueryVirtualRouterOfferingMsg.java | +| 7 | `APIQueryVirtualRouterVmMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIQueryVirtualRouterVmMsg.java | +| 8 | `APIReconnectVirtualRouterMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIReconnectVirtualRouterMsg.java | +| 9 | `APIUpdateVirtualRouterMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIUpdateVirtualRouterMsg.java | +| 10 | `APIUpdateVirtualRouterOfferingMsg` | plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/APIUpdateVirtualRouterOfferingMsg.java | + +--- + + + +## 模块:`plugin/vxlan` + +### `org.zstack.network.l2.vxlan.vtep` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVxlanVtepMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vtep/APICreateVxlanVtepMsg.java | +| 2 | `APIQueryVtepMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vtep/APIQueryVtepMsg.java | + +### `org.zstack.network.l2.vxlan.vxlanNetwork` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateL2VxlanNetworkMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/APICreateL2VxlanNetworkMsg.java | +| 2 | `APIQueryL2VxlanNetworkMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetwork/APIQueryL2VxlanNetworkMsg.java | + +### `org.zstack.network.l2.vxlan.vxlanNetworkPool` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateL2VxlanNetworkPoolMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APICreateL2VxlanNetworkPoolMsg.java | +| 2 | `APICreateVniRangeMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APICreateVniRangeMsg.java | +| 3 | `APICreateVxlanPoolRemoteVtepMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APICreateVxlanPoolRemoteVtepMsg.java | +| 4 | `APIDeleteVniRangeMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APIDeleteVniRangeMsg.java | +| 5 | `APIDeleteVxlanPoolRemoteVtepMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APIDeleteVxlanPoolRemoteVtepMsg.java | +| 6 | `APIQueryL2VxlanNetworkPoolMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APIQueryL2VxlanNetworkPoolMsg.java | +| 7 | `APIQueryVniRangeMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APIQueryVniRangeMsg.java | +| 8 | `APIUpdateVniRangeMsg` | plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/APIUpdateVniRangeMsg.java | + +--- + + + +## 模块:`premium/accesskey` + +### `org.zstack.accessKey` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeAccessKeyStateMsg` | premium/accesskey/src/main/java/org/zstack/accessKey/APIChangeAccessKeyStateMsg.java | +| 2 | `APICreateAccessKeyMsg` | premium/accesskey/src/main/java/org/zstack/accessKey/APICreateAccessKeyMsg.java | +| 3 | `APIDeleteAccessKeyMsg` | premium/accesskey/src/main/java/org/zstack/accessKey/APIDeleteAccessKeyMsg.java | +| 4 | `APIQueryAccessKeyMsg` | premium/accesskey/src/main/java/org/zstack/accessKey/APIQueryAccessKeyMsg.java | + +--- + + + +## 模块:`premium/autoscaling` + +### `org.zstack.autoscaling.group` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeAutoScalingGroupStateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/APIChangeAutoScalingGroupStateMsg.java | +| 2 | `APICreateAutoScalingGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/APICreateAutoScalingGroupMsg.java | +| 3 | `APIDeleteAutoScalingGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/APIDeleteAutoScalingGroupMsg.java | +| 4 | `APIQueryAutoScalingGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/APIQueryAutoScalingGroupMsg.java | +| 5 | `APIUpdateAutoScalingGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/APIUpdateAutoScalingGroupMsg.java | + +### `org.zstack.autoscaling.group.activity` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryAutoScalingGroupActivityMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/activity/APIQueryAutoScalingGroupActivityMsg.java | + +### `org.zstack.autoscaling.group.instance` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteAutoScalingGroupInstanceMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/instance/APIDeleteAutoScalingGroupInstanceMsg.java | +| 2 | `APIQueryAutoScalingGroupInstanceMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/instance/APIQueryAutoScalingGroupInstanceMsg.java | +| 3 | `APIUpdateAutoScalingGroupInstanceMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/instance/APIUpdateAutoScalingGroupInstanceMsg.java | + +### `org.zstack.autoscaling.group.rule` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAutoScalingGroupAddingNewInstanceRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APICreateAutoScalingGroupAddingNewInstanceRuleMsg.java | +| 2 | `APICreateAutoScalingGroupRemovalInstanceRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APICreateAutoScalingGroupRemovalInstanceRuleMsg.java | +| 3 | `APICreateAutoScalingGroupRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APICreateAutoScalingGroupRuleMsg.java | +| 4 | `APIDeleteAutoScalingRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIDeleteAutoScalingRuleMsg.java | +| 5 | `APIExecuteAutoScalingRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIExecuteAutoScalingRuleMsg.java | +| 6 | `APIQueryAutoScalingRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIQueryAutoScalingRuleMsg.java | +| 7 | `APIUpdateAutoScalingGroupAddingNewInstanceRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIUpdateAutoScalingGroupAddingNewInstanceRuleMsg.java | +| 8 | `APIUpdateAutoScalingGroupRemovalInstanceRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIUpdateAutoScalingGroupRemovalInstanceRuleMsg.java | +| 9 | `APIUpdateAutoScalingRuleMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/APIUpdateAutoScalingRuleMsg.java | + +### `org.zstack.autoscaling.group.rule.trigger` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAutoScalingRuleAlarmTriggerMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/trigger/APICreateAutoScalingRuleAlarmTriggerMsg.java | +| 2 | `APICreateAutoScalingRuleSchedulerJobTriggerMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/trigger/APICreateAutoScalingRuleSchedulerJobTriggerMsg.java | +| 3 | `APICreateAutoScalingRuleTriggerMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/trigger/APICreateAutoScalingRuleTriggerMsg.java | +| 4 | `APIDeleteAutoScalingRuleTriggerMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/trigger/APIDeleteAutoScalingRuleTriggerMsg.java | +| 5 | `APIQueryAutoScalingRuleTriggerMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/group/rule/trigger/APIQueryAutoScalingRuleTriggerMsg.java | + +### `org.zstack.autoscaling.template` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachAutoScalingTemplateToGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APIAttachAutoScalingTemplateToGroupMsg.java | +| 2 | `APICreateAutoScalingTemplateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APICreateAutoScalingTemplateMsg.java | +| 3 | `APICreateAutoScalingVmTemplateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APICreateAutoScalingVmTemplateMsg.java | +| 4 | `APIDeleteAutoScalingTemplateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APIDeleteAutoScalingTemplateMsg.java | +| 5 | `APIDetachAutoScalingTemplateFromGroupMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APIDetachAutoScalingTemplateFromGroupMsg.java | +| 6 | `APIQueryAutoScalingVmTemplateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APIQueryAutoScalingVmTemplateMsg.java | +| 7 | `APIUpdateAutoScalingVmTemplateMsg` | premium/autoscaling/src/main/java/org/zstack/autoscaling/template/APIUpdateAutoScalingVmTemplateMsg.java | + +--- + + + +## 模块:`premium/baremetal` + +### `org.zstack.header.baremetal.chassis` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBatchCreateBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIBatchCreateBaremetalChassisMsg.java | +| 2 | `APIChangeBaremetalChassisStateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIChangeBaremetalChassisStateMsg.java | +| 3 | `APICheckBaremetalChassisConfigFileMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APICheckBaremetalChassisConfigFileMsg.java | +| 4 | `APICleanUpBaremetalChassisBondingMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APICleanUpBaremetalChassisBondingMsg.java | +| 5 | `APICreateBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APICreateBaremetalChassisMsg.java | +| 6 | `APIDeleteBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIDeleteBaremetalChassisMsg.java | +| 7 | `APIGetBaremetalChassisPowerStatusMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIGetBaremetalChassisPowerStatusMsg.java | +| 8 | `APIInspectBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIInspectBaremetalChassisMsg.java | +| 9 | `APIPowerOffBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIPowerOffBaremetalChassisMsg.java | +| 10 | `APIPowerOnBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIPowerOnBaremetalChassisMsg.java | +| 11 | `APIPowerResetBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIPowerResetBaremetalChassisMsg.java | +| 12 | `APIQueryBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIQueryBaremetalChassisMsg.java | +| 13 | `APIUpdateBaremetalChassisMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/chassis/APIUpdateBaremetalChassisMsg.java | + +### `org.zstack.header.baremetal.instance` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APICreateBaremetalInstanceMsg.java | +| 2 | `APIDestroyBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIDestroyBaremetalInstanceMsg.java | +| 3 | `APIExpungeBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIExpungeBaremetalInstanceMsg.java | +| 4 | `APIQueryBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIQueryBaremetalInstanceMsg.java | +| 5 | `APIRebootBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIRebootBaremetalInstanceMsg.java | +| 6 | `APIRecoverBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIRecoverBaremetalInstanceMsg.java | +| 7 | `APIStartBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIStartBaremetalInstanceMsg.java | +| 8 | `APIStopBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIStopBaremetalInstanceMsg.java | +| 9 | `APIUpdateBaremetalInstanceMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/instance/APIUpdateBaremetalInstanceMsg.java | + +### `org.zstack.header.baremetal.network` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateBaremetalBondingMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/network/APICreateBaremetalBondingMsg.java | +| 2 | `APIQueryBaremetalBondingMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/network/APIQueryBaremetalBondingMsg.java | + +### `org.zstack.header.baremetal.preconfiguration` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddPreconfigurationTemplateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/preconfiguration/APIAddPreconfigurationTemplateMsg.java | +| 2 | `APIChangePreconfigurationTemplateStateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/preconfiguration/APIChangePreconfigurationTemplateStateMsg.java | +| 3 | `APIDeletePreconfigurationTemplateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/preconfiguration/APIDeletePreconfigurationTemplateMsg.java | +| 4 | `APIQueryPreconfigurationTemplateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/preconfiguration/APIQueryPreconfigurationTemplateMsg.java | +| 5 | `APIUpdatePreconfigurationTemplateMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/preconfiguration/APIUpdatePreconfigurationTemplateMsg.java | + +### `org.zstack.header.baremetal.pxeserver` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachBaremetalPxeServerToClusterMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIAttachBaremetalPxeServerToClusterMsg.java | +| 2 | `APICreateBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APICreateBaremetalPxeServerMsg.java | +| 3 | `APIDeleteBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIDeleteBaremetalPxeServerMsg.java | +| 4 | `APIDetachBaremetalPxeServerFromClusterMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIDetachBaremetalPxeServerFromClusterMsg.java | +| 5 | `APIQueryBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIQueryBaremetalPxeServerMsg.java | +| 6 | `APIReconnectBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIReconnectBaremetalPxeServerMsg.java | +| 7 | `APIStartBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIStartBaremetalPxeServerMsg.java | +| 8 | `APIStopBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIStopBaremetalPxeServerMsg.java | +| 9 | `APIUpdateBaremetalPxeServerMsg` | premium/baremetal/src/main/java/org/zstack/header/baremetal/pxeserver/APIUpdateBaremetalPxeServerMsg.java | + +--- + + + +## 模块:`premium/baremetal2` + +### `org.zstack.baremetal2.chassis` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIAddBareMetal2ChassisMsg.java | +| 2 | `APIBatchAddBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIBatchAddBareMetal2ChassisMsg.java | +| 3 | `APIChangeBareMetal2ChassisStateMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIChangeBareMetal2ChassisStateMsg.java | +| 4 | `APICheckBareMetal2ChassisConfigFileMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APICheckBareMetal2ChassisConfigFileMsg.java | +| 5 | `APICleanUpBareMetal2BondingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APICleanUpBareMetal2BondingMsg.java | +| 6 | `APICreateBareMetal2BondingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APICreateBareMetal2BondingMsg.java | +| 7 | `APICreateBareMetal2ChassisHardwareInfoMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APICreateBareMetal2ChassisHardwareInfoMsg.java | +| 8 | `APIDeleteBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIDeleteBareMetal2ChassisMsg.java | +| 9 | `APIGetBareMetal2ChassisPowerStatusMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIGetBareMetal2ChassisPowerStatusMsg.java | +| 10 | `APIGetBareMetal2SupportedBootModeMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIGetBareMetal2SupportedBootModeMsg.java | +| 11 | `APIInspectBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIInspectBareMetal2ChassisMsg.java | +| 12 | `APIPowerOffBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIPowerOffBareMetal2ChassisMsg.java | +| 13 | `APIPowerOnBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIPowerOnBareMetal2ChassisMsg.java | +| 14 | `APIPowerResetBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIPowerResetBareMetal2ChassisMsg.java | +| 15 | `APIQueryBareMetal2BondingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIQueryBareMetal2BondingMsg.java | +| 16 | `APIQueryBareMetal2BondingNicRefMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIQueryBareMetal2BondingNicRefMsg.java | +| 17 | `APIQueryBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIQueryBareMetal2ChassisMsg.java | +| 18 | `APIUpdateBareMetal2ChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/APIUpdateBareMetal2ChassisMsg.java | + +### `org.zstack.baremetal2.chassis.ipmi` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBareMetal2IpmiChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/ipmi/APIAddBareMetal2IpmiChassisMsg.java | +| 2 | `APIBatchAddBareMetal2IpmiChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/ipmi/APIBatchAddBareMetal2IpmiChassisMsg.java | +| 3 | `APICheckBareMetal2IpmiChassisConfigFileMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/ipmi/APICheckBareMetal2IpmiChassisConfigFileMsg.java | +| 4 | `APICreateBareMetal2IpmiChassisHardwareInfoMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/ipmi/APICreateBareMetal2IpmiChassisHardwareInfoMsg.java | +| 5 | `APIUpdateBareMetal2IpmiChassisMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/chassis/ipmi/APIUpdateBareMetal2IpmiChassisMsg.java | + +### `org.zstack.baremetal2.configuration` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeBareMetal2ChassisOfferingStateMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/configuration/APIChangeBareMetal2ChassisOfferingStateMsg.java | +| 2 | `APIQueryBareMetal2ChassisOfferingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/configuration/APIQueryBareMetal2ChassisOfferingMsg.java | +| 3 | `APIUpdateBareMetal2ChassisOfferingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/configuration/APIUpdateBareMetal2ChassisOfferingMsg.java | + +### `org.zstack.baremetal2.gateway` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBareMetal2GatewayMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIAddBareMetal2GatewayMsg.java | +| 2 | `APIAttachBareMetal2GatewayToClusterMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIAttachBareMetal2GatewayToClusterMsg.java | +| 3 | `APIChangeBareMetal2GatewayClusterMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIChangeBareMetal2GatewayClusterMsg.java | +| 4 | `APIChangeBareMetal2GatewayStateMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIChangeBareMetal2GatewayStateMsg.java | +| 5 | `APIDeleteBareMetal2GatewayMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIDeleteBareMetal2GatewayMsg.java | +| 6 | `APIDetachBareMetal2GatewayFromClusterMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIDetachBareMetal2GatewayFromClusterMsg.java | +| 7 | `APIQueryBareMetal2GatewayMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIQueryBareMetal2GatewayMsg.java | +| 8 | `APIReconnectBareMetal2GatewayMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIReconnectBareMetal2GatewayMsg.java | +| 9 | `APIUpdateBareMetal2GatewayMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/APIUpdateBareMetal2GatewayMsg.java | + +### `org.zstack.baremetal2.gateway.allocator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetBareMetal2GatewayAllocatorStrategiesMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/gateway/allocator/APIGetBareMetal2GatewayAllocatorStrategiesMsg.java | + +### `org.zstack.baremetal2.instance` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachProvisionNicToBondingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIAttachProvisionNicToBondingMsg.java | +| 2 | `APIChangeBareMetal2InstancePasswordMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIChangeBareMetal2InstancePasswordMsg.java | +| 3 | `APICreateBareMetal2InstanceMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APICreateBareMetal2InstanceMsg.java | +| 4 | `APIDetachProvisionNicFromBondingMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIDetachProvisionNicFromBondingMsg.java | +| 5 | `APIQueryBareMetal2InstanceMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIQueryBareMetal2InstanceMsg.java | +| 6 | `APIReconnectBareMetal2InstanceMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIReconnectBareMetal2InstanceMsg.java | +| 7 | `APIStartBareMetal2InstanceMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIStartBareMetal2InstanceMsg.java | +| 8 | `APIUpdateBareMetal2InstanceMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/instance/APIUpdateBareMetal2InstanceMsg.java | + +### `org.zstack.baremetal2.provisionnetwork` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachBareMetal2ProvisionNetworkToClusterMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIAttachBareMetal2ProvisionNetworkToClusterMsg.java | +| 2 | `APIChangeBareMetal2ProvisionNetworkStateMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIChangeBareMetal2ProvisionNetworkStateMsg.java | +| 3 | `APICreateBareMetal2ProvisionNetworkMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APICreateBareMetal2ProvisionNetworkMsg.java | +| 4 | `APIDeleteBareMetal2ProvisionNetworkMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIDeleteBareMetal2ProvisionNetworkMsg.java | +| 5 | `APIDetachBareMetal2ProvisionNetworkFromClusterMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIDetachBareMetal2ProvisionNetworkFromClusterMsg.java | +| 6 | `APIGetBareMetal2ProvisionNetworkIpAddressCapacityMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIGetBareMetal2ProvisionNetworkIpAddressCapacityMsg.java | +| 7 | `APIQueryBareMetal2ProvisionNetworkMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIQueryBareMetal2ProvisionNetworkMsg.java | +| 8 | `APIUpdateBareMetal2ProvisionNetworkMsg` | premium/baremetal2/src/main/java/org/zstack/baremetal2/provisionnetwork/APIUpdateBareMetal2ProvisionNetworkMsg.java | + +--- + + + +## 模块:`premium/billing` + +### `org.zstack.billing` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICalculateAccountBillingSpendingMsg` | premium/billing/src/main/java/org/zstack/billing/APICalculateAccountBillingSpendingMsg.java | +| 2 | `APICalculateAccountSpendingMsg` | premium/billing/src/main/java/org/zstack/billing/APICalculateAccountSpendingMsg.java | +| 3 | `APICalculateResourceSpendingMsg` | premium/billing/src/main/java/org/zstack/billing/APICalculateResourceSpendingMsg.java | +| 4 | `APICleanupBillingUsageMsg` | premium/billing/src/main/java/org/zstack/billing/APICleanupBillingUsageMsg.java | +| 5 | `APICreateResourcePriceMsg` | premium/billing/src/main/java/org/zstack/billing/APICreateResourcePriceMsg.java | +| 6 | `APIDeleteBillingMsg` | premium/billing/src/main/java/org/zstack/billing/APIDeleteBillingMsg.java | +| 7 | `APIDeleteResourcePriceMsg` | premium/billing/src/main/java/org/zstack/billing/APIDeleteResourcePriceMsg.java | +| 8 | `APIQueryAccountBillingMsg` | premium/billing/src/main/java/org/zstack/billing/APIQueryAccountBillingMsg.java | +| 9 | `APIQueryResourcePriceMsg` | premium/billing/src/main/java/org/zstack/billing/APIQueryResourcePriceMsg.java | +| 10 | `APIUpdateResourcePriceMsg` | premium/billing/src/main/java/org/zstack/billing/APIUpdateResourcePriceMsg.java | + +### `org.zstack.billing.generator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGenerateAccountBillingMsg` | premium/billing/src/main/java/org/zstack/billing/generator/APIGenerateAccountBillingMsg.java | + +### `org.zstack.billing.table` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachPriceTableToAccountMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIAttachPriceTableToAccountMsg.java | +| 2 | `APIChangeAccountPriceTableBindingMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIChangeAccountPriceTableBindingMsg.java | +| 3 | `APICreatePriceTableMsg` | premium/billing/src/main/java/org/zstack/billing/table/APICreatePriceTableMsg.java | +| 4 | `APIDeletePriceTableMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIDeletePriceTableMsg.java | +| 5 | `APIDetachPriceTableFromAccountMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIDetachPriceTableFromAccountMsg.java | +| 6 | `APIGetAccountPriceTableRefMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIGetAccountPriceTableRefMsg.java | +| 7 | `APIQueryAccountPriceTableRefMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIQueryAccountPriceTableRefMsg.java | +| 8 | `APIQueryPriceTableMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIQueryPriceTableMsg.java | +| 9 | `APIUpdatePriceTableMsg` | premium/billing/src/main/java/org/zstack/billing/table/APIUpdatePriceTableMsg.java | + +### `org.zstack.billing.userconfig` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIValidateDiskOfferingUserConfigMsg` | premium/billing/src/main/java/org/zstack/billing/userconfig/APIValidateDiskOfferingUserConfigMsg.java | +| 2 | `APIValidateInstanceOfferingUserConfigMsg` | premium/billing/src/main/java/org/zstack/billing/userconfig/APIValidateInstanceOfferingUserConfigMsg.java | +| 3 | `APIValidatePriceUserConfigMsg` | premium/billing/src/main/java/org/zstack/billing/userconfig/APIValidatePriceUserConfigMsg.java | + +--- + + + +## 模块:`premium/cbt` + +### `org.zstack.header.cbt` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateCbtTaskMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APICreateCbtTaskMsg.java | +| 2 | `APIDeleteCbtTaskMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIDeleteCbtTaskMsg.java | +| 3 | `APIDisableCbtTaskMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIDisableCbtTaskMsg.java | +| 4 | `APIEnableCbtTaskMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIEnableCbtTaskMsg.java | +| 5 | `APIExportNbdVolumesMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIExportNbdVolumesMsg.java | +| 6 | `APIQueryCbtTaskMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIQueryCbtTaskMsg.java | +| 7 | `APIUnexportNbdVolumesMsg` | premium/cbt/src/main/java/org/zstack/header/cbt/APIUnexportNbdVolumesMsg.java | + +--- + + + +## 模块:`premium/cdp` + +### `org.zstack.header.storage.cdp` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateCdpPolicyMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APICreateCdpPolicyMsg.java | +| 2 | `APICreateCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APICreateCdpTaskMsg.java | +| 3 | `APICreateVmFromCdpBackupMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APICreateVmFromCdpBackupMsg.java | +| 4 | `APIDeleteCdpPolicyMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIDeleteCdpPolicyMsg.java | +| 5 | `APIDeleteCdpTaskDataMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIDeleteCdpTaskDataMsg.java | +| 6 | `APIDeleteCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIDeleteCdpTaskMsg.java | +| 7 | `APIDescribeVmInstanceRecoveryPointMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIDescribeVmInstanceRecoveryPointMsg.java | +| 8 | `APIDisableCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIDisableCdpTaskMsg.java | +| 9 | `APIEnableCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIEnableCdpTaskMsg.java | +| 10 | `APIGetVmInstanceProtectedRecoveryPointsMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIGetVmInstanceProtectedRecoveryPointsMsg.java | +| 11 | `APIGetVmInstanceRecoveryPointsMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIGetVmInstanceRecoveryPointsMsg.java | +| 12 | `APIMergeDataOnBackupStorageMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIMergeDataOnBackupStorageMsg.java | +| 13 | `APIMountVmInstanceRecoveryPointMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIMountVmInstanceRecoveryPointMsg.java | +| 14 | `APIProtectVmInstanceRecoveryPointMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIProtectVmInstanceRecoveryPointMsg.java | +| 15 | `APIQueryCdpPolicyMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIQueryCdpPolicyMsg.java | +| 16 | `APIQueryCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIQueryCdpTaskMsg.java | +| 17 | `APIRevertVmFromCdpBackupMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIRevertVmFromCdpBackupMsg.java | +| 18 | `APIUnmountVmInstanceRecoveryPointMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIUnmountVmInstanceRecoveryPointMsg.java | +| 19 | `APIUnprotectVmInstanceRecoveryPointMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIUnprotectVmInstanceRecoveryPointMsg.java | +| 20 | `APIUpdateCdpPolicyMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIUpdateCdpPolicyMsg.java | +| 21 | `APIUpdateCdpTaskMsg` | premium/cdp/src/main/java/org/zstack/header/storage/cdp/APIUpdateCdpTaskMsg.java | + +--- + + + +## 模块:`premium/cloudformation` + +### `org.zstack.header.cloudformation` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddStackTemplateMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIAddStackTemplateMsg.java | +| 2 | `APICheckStackTemplateParametersMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APICheckStackTemplateParametersMsg.java | +| 3 | `APICreateResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APICreateResourceStackMsg.java | +| 4 | `APIDecodeStackTemplateMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIDecodeStackTemplateMsg.java | +| 5 | `APIDeleteResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIDeleteResourceStackMsg.java | +| 6 | `APIDeleteStackTemplateMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIDeleteStackTemplateMsg.java | +| 7 | `APIGetResourceFromResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIGetResourceFromResourceStackMsg.java | +| 8 | `APIGetResourceStackFromResourceMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIGetResourceStackFromResourceMsg.java | +| 9 | `APIGetSupportedCloudFormationResourcesMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIGetSupportedCloudFormationResourcesMsg.java | +| 10 | `APIPreviewResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIPreviewResourceStackMsg.java | +| 11 | `APIQueryEventFromResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIQueryEventFromResourceStackMsg.java | +| 12 | `APIQueryResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIQueryResourceStackMsg.java | +| 13 | `APIQueryStackTemplateMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIQueryStackTemplateMsg.java | +| 14 | `APIRestartResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIRestartResourceStackMsg.java | +| 15 | `APIUpdateResourceStackMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIUpdateResourceStackMsg.java | +| 16 | `APIUpdateStackTemplateMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/APIUpdateStackTemplateMsg.java | + +### `org.zstack.header.cloudformation.monitor` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddResourceStackVmPortMonitorMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/monitor/APIAddResourceStackVmPortMonitorMsg.java | +| 2 | `APIDeleteResourceStackVmPortMonitorMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/monitor/APIDeleteResourceStackVmPortMonitorMsg.java | +| 3 | `APIGetResourceStackVmStatusMsg` | premium/cloudformation/src/main/java/org/zstack/header/cloudformation/monitor/APIGetResourceStackVmStatusMsg.java | + +--- + + + +## 模块:`premium/crypto` + +### `org.zstack.crypto.ccs` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddCCSCertificateMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIAddCCSCertificateMsg.java | +| 2 | `APIAttachCCSCertificateToAccountMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIAttachCCSCertificateToAccountMsg.java | +| 3 | `APIDeleteCCSCertificateMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIDeleteCCSCertificateMsg.java | +| 4 | `APIDetachCCSCertificateFromAccountMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIDetachCCSCertificateFromAccountMsg.java | +| 5 | `APIQueryCCSCertificateMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIQueryCCSCertificateMsg.java | +| 6 | `APIUpdateCCSCertificateAccountStateMsg` | premium/crypto/src/main/java/org/zstack/crypto/ccs/APIUpdateCCSCertificateAccountStateMsg.java | + +### `org.zstack.crypto.datacrypto.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddIntegrityResourceMsg` | premium/crypto/src/main/java/org/zstack/crypto/datacrypto/api/APIAddIntegrityResourceMsg.java | +| 2 | `APICheckBatchDataIntegrityMsg` | premium/crypto/src/main/java/org/zstack/crypto/datacrypto/api/APICheckBatchDataIntegrityMsg.java | +| 3 | `APIStartDataProtectionMsg` | premium/crypto/src/main/java/org/zstack/crypto/datacrypto/api/APIStartDataProtectionMsg.java | + +### `org.zstack.crypto.securitymachine.api.secretresourcepool` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeSecretResourcePoolStateMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIChangeSecretResourcePoolStateMsg.java | +| 2 | `APICreateAiSiNoSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APICreateAiSiNoSecretResourcePoolMsg.java | +| 3 | `APICreateFlkSecSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APICreateFlkSecSecretResourcePoolMsg.java | +| 4 | `APICreateHaiTaiSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APICreateHaiTaiSecretResourcePoolMsg.java | +| 5 | `APICreateInfoSecSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APICreateInfoSecSecretResourcePoolMsg.java | +| 6 | `APICreateSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APICreateSecretResourcePoolMsg.java | +| 7 | `APIDeleteSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIDeleteSecretResourcePoolMsg.java | +| 8 | `APIGetSignatureServerEncryptPublicKeyMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIGetSignatureServerEncryptPublicKeyMsg.java | +| 9 | `APIQuerySecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIQuerySecretResourcePoolMsg.java | +| 10 | `APIUpdateFlkSecSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIUpdateFlkSecSecretResourcePoolMsg.java | +| 11 | `APIUpdateInfoSecSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIUpdateInfoSecSecretResourcePoolMsg.java | +| 12 | `APIUpdateSecretResourcePoolMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/secretresourcepool/APIUpdateSecretResourcePoolMsg.java | + +### `org.zstack.crypto.securitymachine.api.securitymachine` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddFlkSecSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIAddFlkSecSecurityMachineMsg.java | +| 2 | `APIAddInfoSecSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIAddInfoSecSecurityMachineMsg.java | +| 3 | `APIAddSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIAddSecurityMachineMsg.java | +| 4 | `APIChangeSecurityMachineStateMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIChangeSecurityMachineStateMsg.java | +| 5 | `APIDeleteSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIDeleteSecurityMachineMsg.java | +| 6 | `APIQuerySecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIQuerySecurityMachineMsg.java | +| 7 | `APISecurityMachineDetectSyncMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APISecurityMachineDetectSyncMsg.java | +| 8 | `APISetSecurityMachineKeyMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APISetSecurityMachineKeyMsg.java | +| 9 | `APIUpdateFlkSecSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIUpdateFlkSecSecurityMachineMsg.java | +| 10 | `APIUpdateInfoSecSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIUpdateInfoSecSecurityMachineMsg.java | +| 11 | `APIUpdateSecurityMachineMsg` | premium/crypto/src/main/java/org/zstack/crypto/securitymachine/api/securitymachine/APIUpdateSecurityMachineMsg.java | + +--- + + + +## 模块:`premium/drs` + +### `org.zstack.drs.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIApplyDRSAdviceMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIApplyDRSAdviceMsg.java | +| 2 | `APICreateClusterDRSMsg` | premium/drs/src/main/java/org/zstack/drs/api/APICreateClusterDRSMsg.java | +| 3 | `APIDeleteClusterDRSMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIDeleteClusterDRSMsg.java | +| 4 | `APIExecuteDRSSchedulingMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIExecuteDRSSchedulingMsg.java | +| 5 | `APIGetClusterDRSStatusMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIGetClusterDRSStatusMsg.java | +| 6 | `APIQueryClusterDRSMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIQueryClusterDRSMsg.java | +| 7 | `APIQueryDRSAdviceMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIQueryDRSAdviceMsg.java | +| 8 | `APIQueryDRSVmMigrationActivityMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIQueryDRSVmMigrationActivityMsg.java | +| 9 | `APIUpdateClusterDRSMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIUpdateClusterDRSMsg.java | +| 10 | `APIValidateClusterSupportDRSMsg` | premium/drs/src/main/java/org/zstack/drs/api/APIValidateClusterSupportDRSMsg.java | + +--- + + + +## 模块:`premium/externalbackup` + +### `org.zstack.externalbackup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateExternalBackupMsg` | premium/externalbackup/src/main/java/org/zstack/externalbackup/APICreateExternalBackupMsg.java | +| 2 | `APIDeleteExternalBackupMsg` | premium/externalbackup/src/main/java/org/zstack/externalbackup/APIDeleteExternalBackupMsg.java | +| 3 | `APIGetExternalBackupDetailsMsg` | premium/externalbackup/src/main/java/org/zstack/externalbackup/APIGetExternalBackupDetailsMsg.java | +| 4 | `APIQueryExternalBackupMsg` | premium/externalbackup/src/main/java/org/zstack/externalbackup/APIQueryExternalBackupMsg.java | + +--- + + + +## 模块:`premium/faulttolerance` + +### `org.zstack.faulttolerance.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateFaultToleranceVmInstanceMsg` | premium/faulttolerance/src/main/java/org/zstack/faulttolerance/api/APICreateFaultToleranceVmInstanceMsg.java | +| 2 | `APIFailoverFaultToleranceVmMsg` | premium/faulttolerance/src/main/java/org/zstack/faulttolerance/api/APIFailoverFaultToleranceVmMsg.java | +| 3 | `APIGetFaultToleranceVmsMsg` | premium/faulttolerance/src/main/java/org/zstack/faulttolerance/api/APIGetFaultToleranceVmsMsg.java | +| 4 | `APIQueryFaultToleranceVmMsg` | premium/faulttolerance/src/main/java/org/zstack/faulttolerance/api/APIQueryFaultToleranceVmMsg.java | + +--- + + + +## 模块:`premium/guesttools` + +### `org.zstack.guesttools` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachGuestToolsIsoToVmMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIAttachGuestToolsIsoToVmMsg.java | +| 2 | `APIDetachGuestToolsIsoFromVmMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIDetachGuestToolsIsoFromVmMsg.java | +| 3 | `APIGetLatestGuestToolsForVmMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIGetLatestGuestToolsForVmMsg.java | +| 4 | `APIGetVmGuestToolsInfoMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIGetVmGuestToolsInfoMsg.java | +| 5 | `APIQueryGuestToolsStateMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIQueryGuestToolsStateMsg.java | +| 6 | `APIUpdateGuestToolsStateMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIUpdateGuestToolsStateMsg.java | +| 7 | `APIUpdateVmNetworkConfigMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/APIUpdateVmNetworkConfigMsg.java | + +### `org.zstack.guesttools.advanced` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVmCustomSpecificationMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/advanced/APICreateVmCustomSpecificationMsg.java | +| 2 | `APIDeleteVmCustomSpecificationMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/advanced/APIDeleteVmCustomSpecificationMsg.java | +| 3 | `APIQueryVmCustomSpecificationMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/advanced/APIQueryVmCustomSpecificationMsg.java | +| 4 | `APIUpdateVmCustomSpecificationMsg` | premium/guesttools/src/main/java/org/zstack/guesttools/advanced/APIUpdateVmCustomSpecificationMsg.java | + +--- + + + +## 模块:`premium/hybrid` + +### `org.zstack.header.aliyun.account` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAliyunKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/account/APIAddAliyunKeySecretMsg.java | +| 2 | `APIAttachAliyunKeyMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/account/APIAttachAliyunKeyMsg.java | +| 3 | `APIDeleteAliyunKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/account/APIDeleteAliyunKeySecretMsg.java | +| 4 | `APIDetachAliyunKeyMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/account/APIDetachAliyunKeyMsg.java | +| 5 | `APIUpdateAliyunKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/account/APIUpdateAliyunKeySecretMsg.java | + +### `org.zstack.header.aliyun.ecs` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICloneEcsInstanceFromLocalVmMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APICloneEcsInstanceFromLocalVmMsg.java | +| 2 | `APICreateEcsInstanceFromEcsImageMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APICreateEcsInstanceFromEcsImageMsg.java | +| 3 | `APIDeleteAllEcsInstancesFromDataCenterMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIDeleteAllEcsInstancesFromDataCenterMsg.java | +| 4 | `APIDeleteEcsInstanceLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIDeleteEcsInstanceLocalMsg.java | +| 5 | `APIDeleteEcsInstanceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIDeleteEcsInstanceMsg.java | +| 6 | `APIGetEcsInstanceTypeMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIGetEcsInstanceTypeMsg.java | +| 7 | `APIGetEcsInstanceVncUrlMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIGetEcsInstanceVncUrlMsg.java | +| 8 | `APIQueryEcsInstanceFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIQueryEcsInstanceFromLocalMsg.java | +| 9 | `APIRebootEcsInstanceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIRebootEcsInstanceMsg.java | +| 10 | `APIStartEcsInstanceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIStartEcsInstanceMsg.java | +| 11 | `APIStopEcsInstanceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIStopEcsInstanceMsg.java | +| 12 | `APISyncEcsInstanceFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APISyncEcsInstanceFromRemoteMsg.java | +| 13 | `APIUpdateEcsInstanceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIUpdateEcsInstanceMsg.java | +| 14 | `APIUpdateEcsInstanceVncPasswordMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/ecs/APIUpdateEcsInstanceVncPasswordMsg.java | + +### `org.zstack.header.aliyun.image` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateEcsImageFromEcsSnapshotMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APICreateEcsImageFromEcsSnapshotMsg.java | +| 2 | `APICreateEcsImageFromLocalImageMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APICreateEcsImageFromLocalImageMsg.java | +| 3 | `APIDeleteEcsImageLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APIDeleteEcsImageLocalMsg.java | +| 4 | `APIDeleteEcsImageRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APIDeleteEcsImageRemoteMsg.java | +| 5 | `APIGetCreateEcsImageProgressMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APIGetCreateEcsImageProgressMsg.java | +| 6 | `APIQueryEcsImageFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APIQueryEcsImageFromLocalMsg.java | +| 7 | `APISyncEcsImageFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APISyncEcsImageFromRemoteMsg.java | +| 8 | `APIUpdateEcsImageMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/image/APIUpdateEcsImageMsg.java | + +### `org.zstack.header.aliyun.network.connection` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddConnectionAccessPointFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIAddConnectionAccessPointFromRemoteMsg.java | +| 2 | `APICreateAliyunRouterInterfaceRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APICreateAliyunRouterInterfaceRemoteMsg.java | +| 3 | `APICreateConnectionBetweenL3NetworkAndAliyunVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APICreateConnectionBetweenL3NetworkAndAliyunVSwitchMsg.java | +| 4 | `APIDeleteAliyunRouterInterfaceLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIDeleteAliyunRouterInterfaceLocalMsg.java | +| 5 | `APIDeleteAliyunRouterInterfaceRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIDeleteAliyunRouterInterfaceRemoteMsg.java | +| 6 | `APIDeleteConnectionAccessPointLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIDeleteConnectionAccessPointLocalMsg.java | +| 7 | `APIDeleteConnectionBetweenL3NetWorkAndAliyunVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIDeleteConnectionBetweenL3NetWorkAndAliyunVSwitchMsg.java | +| 8 | `APIDeleteVirtualBorderRouterLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIDeleteVirtualBorderRouterLocalMsg.java | +| 9 | `APIGetConnectionAccessPointFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIGetConnectionAccessPointFromRemoteMsg.java | +| 10 | `APIGetConnectionBetweenL3NetworkAndAliyunVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIGetConnectionBetweenL3NetworkAndAliyunVSwitchMsg.java | +| 11 | `APIQueryAliyunRouterInterfaceFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIQueryAliyunRouterInterfaceFromLocalMsg.java | +| 12 | `APIQueryConnectionAccessPointFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIQueryConnectionAccessPointFromLocalMsg.java | +| 13 | `APIQueryConnectionBetweenL3NetworkAndAliyunVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIQueryConnectionBetweenL3NetworkAndAliyunVSwitchMsg.java | +| 14 | `APIQueryVirtualBorderRouterFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIQueryVirtualBorderRouterFromLocalMsg.java | +| 15 | `APIRecoveryVirtualBorderRouterRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIRecoveryVirtualBorderRouterRemoteMsg.java | +| 16 | `APIStartConnectionBetweenAliyunRouterInterfaceMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIStartConnectionBetweenAliyunRouterInterfaceMsg.java | +| 17 | `APISyncAliyunRouterInterfaceFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APISyncAliyunRouterInterfaceFromRemoteMsg.java | +| 18 | `APISyncConnectionAccessPointFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APISyncConnectionAccessPointFromRemoteMsg.java | +| 19 | `APISyncVirtualBorderRouterFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APISyncVirtualBorderRouterFromRemoteMsg.java | +| 20 | `APITerminateVirtualBorderRouterRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APITerminateVirtualBorderRouterRemoteMsg.java | +| 21 | `APIUpdateAliyunRouteInterfaceRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIUpdateAliyunRouteInterfaceRemoteMsg.java | +| 22 | `APIUpdateConnectionBetweenL3NetWorkAndAliyunVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIUpdateConnectionBetweenL3NetWorkAndAliyunVSwitchMsg.java | +| 23 | `APIUpdateVirtualBorderRouterRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/connection/APIUpdateVirtualBorderRouterRemoteMsg.java | + +### `org.zstack.header.aliyun.network.group` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateEcsSecurityGroupRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APICreateEcsSecurityGroupRemoteMsg.java | +| 2 | `APICreateEcsSecurityGroupRuleRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APICreateEcsSecurityGroupRuleRemoteMsg.java | +| 3 | `APIDeleteEcsSecurityGroupInLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIDeleteEcsSecurityGroupInLocalMsg.java | +| 4 | `APIDeleteEcsSecurityGroupRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIDeleteEcsSecurityGroupRemoteMsg.java | +| 5 | `APIDeleteEcsSecurityGroupRuleRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIDeleteEcsSecurityGroupRuleRemoteMsg.java | +| 6 | `APIQueryEcsSecurityGroupFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIQueryEcsSecurityGroupFromLocalMsg.java | +| 7 | `APIQueryEcsSecurityGroupRuleFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIQueryEcsSecurityGroupRuleFromLocalMsg.java | +| 8 | `APISyncEcsSecurityGroupFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APISyncEcsSecurityGroupFromRemoteMsg.java | +| 9 | `APISyncEcsSecurityGroupRuleFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APISyncEcsSecurityGroupRuleFromRemoteMsg.java | +| 10 | `APIUpdateEcsSecurityGroupMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/group/APIUpdateEcsSecurityGroupMsg.java | + +### `org.zstack.header.aliyun.network.vpc` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateEcsVpcRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APICreateEcsVpcRemoteMsg.java | +| 2 | `APICreateEcsVSwitchRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APICreateEcsVSwitchRemoteMsg.java | +| 3 | `APIDeleteEcsVpcInLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIDeleteEcsVpcInLocalMsg.java | +| 4 | `APIDeleteEcsVpcRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIDeleteEcsVpcRemoteMsg.java | +| 5 | `APIDeleteEcsVSwitchInLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIDeleteEcsVSwitchInLocalMsg.java | +| 6 | `APIDeleteEcsVSwitchRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIDeleteEcsVSwitchRemoteMsg.java | +| 7 | `APIQueryEcsVpcFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIQueryEcsVpcFromLocalMsg.java | +| 8 | `APIQueryEcsVSwitchFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIQueryEcsVSwitchFromLocalMsg.java | +| 9 | `APISyncEcsVpcFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APISyncEcsVpcFromRemoteMsg.java | +| 10 | `APISyncEcsVSwitchFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APISyncEcsVSwitchFromRemoteMsg.java | +| 11 | `APIUpdateEcsVpcMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIUpdateEcsVpcMsg.java | +| 12 | `APIUpdateEcsVSwitchMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vpc/APIUpdateEcsVSwitchMsg.java | + +### `org.zstack.header.aliyun.network.vrouter` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAliyunVpcVirtualRouterEntryRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APICreateAliyunVpcVirtualRouterEntryRemoteMsg.java | +| 2 | `APIDeleteAliyunRouteEntryRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APIDeleteAliyunRouteEntryRemoteMsg.java | +| 3 | `APIDeleteVirtualRouterLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APIDeleteVirtualRouterLocalMsg.java | +| 4 | `APIQueryAliyunRouteEntryFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APIQueryAliyunRouteEntryFromLocalMsg.java | +| 5 | `APIQueryAliyunVirtualRouterFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APIQueryAliyunVirtualRouterFromLocalMsg.java | +| 6 | `APISyncAliyunRouteEntryFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APISyncAliyunRouteEntryFromRemoteMsg.java | +| 7 | `APISyncAliyunVirtualRouterFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APISyncAliyunVirtualRouterFromRemoteMsg.java | +| 8 | `APIUpdateAliyunVirtualRouterMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/network/vrouter/APIUpdateAliyunVirtualRouterMsg.java | + +### `org.zstack.header.aliyun.oss` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddOssBucketFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIAddOssBucketFromRemoteMsg.java | +| 2 | `APIAttachOssBucketToEcsDataCenterMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIAttachOssBucketToEcsDataCenterMsg.java | +| 3 | `APICreateOssBackupBucketRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APICreateOssBackupBucketRemoteMsg.java | +| 4 | `APICreateOssBucketRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APICreateOssBucketRemoteMsg.java | +| 5 | `APIDeleteOssBucketFileRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIDeleteOssBucketFileRemoteMsg.java | +| 6 | `APIDeleteOssBucketNameLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIDeleteOssBucketNameLocalMsg.java | +| 7 | `APIDeleteOssBucketRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIDeleteOssBucketRemoteMsg.java | +| 8 | `APIDetachOssBucketFromEcsDataCenterMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIDetachOssBucketFromEcsDataCenterMsg.java | +| 9 | `APIGetOssBackupBucketFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIGetOssBackupBucketFromRemoteMsg.java | +| 10 | `APIGetOssBucketFileFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIGetOssBucketFileFromRemoteMsg.java | +| 11 | `APIGetOssBucketNameFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIGetOssBucketNameFromRemoteMsg.java | +| 12 | `APIQueryOssBucketFileNameMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIQueryOssBucketFileNameMsg.java | +| 13 | `APIUpdateOssBucketMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/oss/APIUpdateOssBucketMsg.java | + +### `org.zstack.header.aliyun.storage.disk` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachAliyunDiskToEcsMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIAttachAliyunDiskToEcsMsg.java | +| 2 | `APICreateAliyunDiskFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APICreateAliyunDiskFromRemoteMsg.java | +| 3 | `APIDeleteAliyunDiskFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIDeleteAliyunDiskFromLocalMsg.java | +| 4 | `APIDeleteAliyunDiskFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIDeleteAliyunDiskFromRemoteMsg.java | +| 5 | `APIDetachAliyunDiskFromEcsMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIDetachAliyunDiskFromEcsMsg.java | +| 6 | `APIQueryAliyunDiskFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIQueryAliyunDiskFromLocalMsg.java | +| 7 | `APISyncDiskFromAliyunFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APISyncDiskFromAliyunFromRemoteMsg.java | +| 8 | `APIUpdateAliyunDiskMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/disk/APIUpdateAliyunDiskMsg.java | + +### `org.zstack.header.aliyun.storage.snapshot` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAliyunSnapshotRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APICreateAliyunSnapshotRemoteMsg.java | +| 2 | `APIDeleteAliyunSnapshotFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APIDeleteAliyunSnapshotFromLocalMsg.java | +| 3 | `APIDeleteAliyunSnapshotFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APIDeleteAliyunSnapshotFromRemoteMsg.java | +| 4 | `APIGCAliyunSnapshotRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APIGCAliyunSnapshotRemoteMsg.java | +| 5 | `APIQueryAliyunSnapshotFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APIQueryAliyunSnapshotFromLocalMsg.java | +| 6 | `APISyncAliyunSnapshotRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APISyncAliyunSnapshotRemoteMsg.java | +| 7 | `APIUpdateAliyunSnapshotMsg` | premium/hybrid/src/main/java/org/zstack/header/aliyun/storage/snapshot/APIUpdateAliyunSnapshotMsg.java | + +### `org.zstack.header.datacenter` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddDataCenterFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/datacenter/APIAddDataCenterFromRemoteMsg.java | +| 2 | `APIDeleteDataCenterInLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/datacenter/APIDeleteDataCenterInLocalMsg.java | +| 3 | `APIGetDataCenterFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/datacenter/APIGetDataCenterFromRemoteMsg.java | +| 4 | `APIQueryDataCenterFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/datacenter/APIQueryDataCenterFromLocalMsg.java | +| 5 | `APISyncDataCenterFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/datacenter/APISyncDataCenterFromRemoteMsg.java | + +### `org.zstack.header.hybrid.account` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddHybridKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIAddHybridKeySecretMsg.java | +| 2 | `APIAttachHybridKeyMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIAttachHybridKeyMsg.java | +| 3 | `APIDeleteHybridKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIDeleteHybridKeySecretMsg.java | +| 4 | `APIDetachHybridKeyMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIDetachHybridKeyMsg.java | +| 5 | `APIQueryHybridKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIQueryHybridKeySecretMsg.java | +| 6 | `APIUpdateHybridKeySecretMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/account/APIUpdateHybridKeySecretMsg.java | + +### `org.zstack.header.hybrid.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBackupDatabaseToPublicCloudMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/backup/APIBackupDatabaseToPublicCloudMsg.java | +| 2 | `APIDeleteBackupFileInPublicMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/backup/APIDeleteBackupFileInPublicMsg.java | +| 3 | `APIDownloadBackupFileFromPublicCloudMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/backup/APIDownloadBackupFileFromPublicCloudMsg.java | + +### `org.zstack.header.hybrid.network.eip` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachHybridEipToEcsMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIAttachHybridEipToEcsMsg.java | +| 2 | `APICreateHybridEipMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APICreateHybridEipMsg.java | +| 3 | `APIDeleteHybridEipFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIDeleteHybridEipFromLocalMsg.java | +| 4 | `APIDeleteHybridEipRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIDeleteHybridEipRemoteMsg.java | +| 5 | `APIDetachHybridEipFromEcsMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIDetachHybridEipFromEcsMsg.java | +| 6 | `APIQueryHybridEipFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIQueryHybridEipFromLocalMsg.java | +| 7 | `APISyncHybridEipFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APISyncHybridEipFromRemoteMsg.java | +| 8 | `APIUpdateHybridEipMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/eip/APIUpdateHybridEipMsg.java | + +### `org.zstack.header.hybrid.network.vpn` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVpcUserVpnGatewayRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APICreateVpcUserVpnGatewayRemoteMsg.java | +| 2 | `APICreateVpcVpnConnectionRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APICreateVpcVpnConnectionRemoteMsg.java | +| 3 | `APICreateVpnIkeConfigMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APICreateVpnIkeConfigMsg.java | +| 4 | `APICreateVpnIpsecConfigMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APICreateVpnIpsecConfigMsg.java | +| 5 | `APIDeleteVpcIkeConfigLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcIkeConfigLocalMsg.java | +| 6 | `APIDeleteVpcIpSecConfigLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcIpSecConfigLocalMsg.java | +| 7 | `APIDeleteVpcUserVpnGatewayLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcUserVpnGatewayLocalMsg.java | +| 8 | `APIDeleteVpcUserVpnGatewayRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcUserVpnGatewayRemoteMsg.java | +| 9 | `APIDeleteVpcVpnConnectionLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcVpnConnectionLocalMsg.java | +| 10 | `APIDeleteVpcVpnConnectionRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcVpnConnectionRemoteMsg.java | +| 11 | `APIDeleteVpcVpnGatewayLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIDeleteVpcVpnGatewayLocalMsg.java | +| 12 | `APIGetVpcVpnConfigurationFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIGetVpcVpnConfigurationFromRemoteMsg.java | +| 13 | `APIQueryVpcIkeConfigFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIQueryVpcIkeConfigFromLocalMsg.java | +| 14 | `APIQueryVpcIpSecConfigFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIQueryVpcIpSecConfigFromLocalMsg.java | +| 15 | `APIQueryVpcUserVpnGatewayFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIQueryVpcUserVpnGatewayFromLocalMsg.java | +| 16 | `APIQueryVpcVpnConnectionFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIQueryVpcVpnConnectionFromLocalMsg.java | +| 17 | `APIQueryVpcVpnGatewayFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIQueryVpcVpnGatewayFromLocalMsg.java | +| 18 | `APISyncVpcUserVpnGatewayFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APISyncVpcUserVpnGatewayFromRemoteMsg.java | +| 19 | `APISyncVpcVpnConnectionFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APISyncVpcVpnConnectionFromRemoteMsg.java | +| 20 | `APISyncVpcVpnGatewayFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APISyncVpcVpnGatewayFromRemoteMsg.java | +| 21 | `APIUpdateVpcUserVpnGatewayMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIUpdateVpcUserVpnGatewayMsg.java | +| 22 | `APIUpdateVpcVpnConnectionRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIUpdateVpcVpnConnectionRemoteMsg.java | +| 23 | `APIUpdateVpcVpnGatewayMsg` | premium/hybrid/src/main/java/org/zstack/header/hybrid/network/vpn/APIUpdateVpcVpnGatewayMsg.java | + +### `org.zstack.header.identityzone` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddIdentityZoneFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/identityzone/APIAddIdentityZoneFromRemoteMsg.java | +| 2 | `APIDeleteIdentityZoneInLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/identityzone/APIDeleteIdentityZoneInLocalMsg.java | +| 3 | `APIGetIdentityZoneFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/identityzone/APIGetIdentityZoneFromRemoteMsg.java | +| 4 | `APIQueryIdentityZoneFromLocalMsg` | premium/hybrid/src/main/java/org/zstack/header/identityzone/APIQueryIdentityZoneFromLocalMsg.java | +| 5 | `APISyncIdentityFromRemoteMsg` | premium/hybrid/src/main/java/org/zstack/header/identityzone/APISyncIdentityFromRemoteMsg.java | + +--- + + + +## 模块:`premium/iam1` + +### `org.zstack.iam1.api.accounts` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAccountToGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIAddAccountToGroupMsg.java | +| 2 | `APIAttachRoleToAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIAttachRoleToAccountGroupMsg.java | +| 3 | `APICreateAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APICreateAccountGroupMsg.java | +| 4 | `APIDeleteAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIDeleteAccountGroupMsg.java | +| 5 | `APIDetachRoleFromAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIDetachRoleFromAccountGroupMsg.java | +| 6 | `APIGetAccountGroupTreeMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIGetAccountGroupTreeMsg.java | +| 7 | `APIGetResourceInAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIGetResourceInAccountGroupMsg.java | +| 8 | `APIGetRolesForAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIGetRolesForAccountGroupMsg.java | +| 9 | `APIMoveAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIMoveAccountGroupMsg.java | +| 10 | `APIQueryAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIQueryAccountGroupMsg.java | +| 11 | `APIRemoveAccountFromGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIRemoveAccountFromGroupMsg.java | +| 12 | `APIRevokeResourceSharingToGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIRevokeResourceSharingToGroupMsg.java | +| 13 | `APIShareResourceToGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIShareResourceToGroupMsg.java | +| 14 | `APIUpdateAccountGroupMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/accounts/APIUpdateAccountGroupMsg.java | + +### `org.zstack.iam1.api.ensemble` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetResourceEnsembleMembersMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/ensemble/APIGetResourceEnsembleMembersMsg.java | +| 2 | `APIGetResourceSharingMsg` | premium/iam1/src/main/java/org/zstack/iam1/api/ensemble/APIGetResourceSharingMsg.java | + +--- + + + +## 模块:`premium/iam2` + +### `org.zstack.iam2.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAttributesToIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddAttributesToIAM2OrganizationMsg.java | +| 2 | `APIAddAttributesToIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddAttributesToIAM2ProjectMsg.java | +| 3 | `APIAddAttributesToIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddAttributesToIAM2VirtualIDGroupMsg.java | +| 4 | `APIAddAttributesToIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddAttributesToIAM2VirtualIDMsg.java | +| 5 | `APIAddIAM2AttributesMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2AttributesMsg.java | +| 6 | `APIAddIAM2VirtualIDGroupToProjectsMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2VirtualIDGroupToProjectsMsg.java | +| 7 | `APIAddIAM2VirtualIDsToGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2VirtualIDsToGroupMsg.java | +| 8 | `APIAddIAM2VirtualIDsToOrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2VirtualIDsToOrganizationMsg.java | +| 9 | `APIAddIAM2VirtualIDsToProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2VirtualIDsToProjectMsg.java | +| 10 | `APIAddIAM2VirtualIDsToProjectsMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddIAM2VirtualIDsToProjectsMsg.java | +| 11 | `APIAddResourceToIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddResourceToIAM2ProjectMsg.java | +| 12 | `APIAddRolesToIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddRolesToIAM2VirtualIDGroupMsg.java | +| 13 | `APIAddRolesToIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAddRolesToIAM2VirtualIDMsg.java | +| 14 | `APIAttachIAM2ProjectToIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIAttachIAM2ProjectToIAM2OrganizationMsg.java | +| 15 | `APIBatchCreateIAM2VirtualIDFromConfigFileMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIBatchCreateIAM2VirtualIDFromConfigFileMsg.java | +| 16 | `APIChangeIAM2OrganizationParentMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2OrganizationParentMsg.java | +| 17 | `APIChangeIAM2OrganizationStateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2OrganizationStateMsg.java | +| 18 | `APIChangeIAM2ProjectStateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2ProjectStateMsg.java | +| 19 | `APIChangeIAM2VirtualIDGroupStateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2VirtualIDGroupStateMsg.java | +| 20 | `APIChangeIAM2VirtualIDStateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2VirtualIDStateMsg.java | +| 21 | `APIChangeIAM2VirtualIDTypeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIChangeIAM2VirtualIDTypeMsg.java | +| 22 | `APICheckIAM2OrganizationAvailabilityMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICheckIAM2OrganizationAvailabilityMsg.java | +| 23 | `APICheckIAM2VirtualIDConfigFileMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICheckIAM2VirtualIDConfigFileMsg.java | +| 24 | `APICreateIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2OrganizationMsg.java | +| 25 | `APICreateIAM2ProjectFromTemplateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2ProjectFromTemplateMsg.java | +| 26 | `APICreateIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2ProjectMsg.java | +| 27 | `APICreateIAM2ProjectRoleMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2ProjectRoleMsg.java | +| 28 | `APICreateIAM2ProjectTemplateFromProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2ProjectTemplateFromProjectMsg.java | +| 29 | `APICreateIAM2ProjectTemplateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2ProjectTemplateMsg.java | +| 30 | `APICreateIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2VirtualIDGroupMsg.java | +| 31 | `APICreateIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APICreateIAM2VirtualIDMsg.java | +| 32 | `APIDeleteIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDeleteIAM2OrganizationMsg.java | +| 33 | `APIDeleteIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDeleteIAM2ProjectMsg.java | +| 34 | `APIDeleteIAM2ProjectTemplateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDeleteIAM2ProjectTemplateMsg.java | +| 35 | `APIDeleteIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDeleteIAM2VirtualIDGroupMsg.java | +| 36 | `APIDeleteIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDeleteIAM2VirtualIDMsg.java | +| 37 | `APIDetachIAM2ProjectFromIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIDetachIAM2ProjectFromIAM2OrganizationMsg.java | +| 38 | `APIExpungeIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIExpungeIAM2ProjectMsg.java | +| 39 | `APIGetIAM2OrganizationVirtualIDNumberMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetIAM2OrganizationVirtualIDNumberMsg.java | +| 40 | `APIGetIAM2ProjectsOfVirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetIAM2ProjectsOfVirtualIDMsg.java | +| 41 | `APIGetIAM2SystemAttributesMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetIAM2SystemAttributesMsg.java | +| 42 | `APIGetIAM2VirtualIDAPIPermissionMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetIAM2VirtualIDAPIPermissionMsg.java | +| 43 | `APIGetIAM2VirtualIDInGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetIAM2VirtualIDInGroupMsg.java | +| 44 | `APIGetOrganizationQuotaUsageMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIGetOrganizationQuotaUsageMsg.java | +| 45 | `APILoginIAM2PlatformMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APILoginIAM2PlatformMsg.java | +| 46 | `APILoginIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APILoginIAM2ProjectMsg.java | +| 47 | `APILoginIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APILoginIAM2VirtualIDMsg.java | +| 48 | `APIQueryIAM2OrganizationAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2OrganizationAttributeMsg.java | +| 49 | `APIQueryIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2OrganizationMsg.java | +| 50 | `APIQueryIAM2OrganizationProjectRefMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2OrganizationProjectRefMsg.java | +| 51 | `APIQueryIAM2ProjectAccountRefMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2ProjectAccountRefMsg.java | +| 52 | `APIQueryIAM2ProjectAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2ProjectAttributeMsg.java | +| 53 | `APIQueryIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2ProjectMsg.java | +| 54 | `APIQueryIAM2ProjectRoleMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2ProjectRoleMsg.java | +| 55 | `APIQueryIAM2ProjectTemplateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2ProjectTemplateMsg.java | +| 56 | `APIQueryIAM2VirtualIDAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2VirtualIDAttributeMsg.java | +| 57 | `APIQueryIAM2VirtualIDGroupAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2VirtualIDGroupAttributeMsg.java | +| 58 | `APIQueryIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2VirtualIDGroupMsg.java | +| 59 | `APIQueryIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIQueryIAM2VirtualIDMsg.java | +| 60 | `APIRecoverIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRecoverIAM2ProjectMsg.java | +| 61 | `APIRemoveAttributesFromIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveAttributesFromIAM2OrganizationMsg.java | +| 62 | `APIRemoveAttributesFromIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveAttributesFromIAM2ProjectMsg.java | +| 63 | `APIRemoveAttributesFromIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveAttributesFromIAM2VirtualIDGroupMsg.java | +| 64 | `APIRemoveAttributesFromIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveAttributesFromIAM2VirtualIDMsg.java | +| 65 | `APIRemoveIAM2ProjectLoginExpiredMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2ProjectLoginExpiredMsg.java | +| 66 | `APIRemoveIAM2VirtualIDGroupFromProjectsMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2VirtualIDGroupFromProjectsMsg.java | +| 67 | `APIRemoveIAM2VirtualIDsFromGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2VirtualIDsFromGroupMsg.java | +| 68 | `APIRemoveIAM2VirtualIDsFromOrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2VirtualIDsFromOrganizationMsg.java | +| 69 | `APIRemoveIAM2VirtualIDsFromProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2VirtualIDsFromProjectMsg.java | +| 70 | `APIRemoveIAM2VirtualIDsFromProjectsMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveIAM2VirtualIDsFromProjectsMsg.java | +| 71 | `APIRemoveRolesFromIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveRolesFromIAM2VirtualIDGroupMsg.java | +| 72 | `APIRemoveRolesFromIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIRemoveRolesFromIAM2VirtualIDMsg.java | +| 73 | `APISetIAM2ProjectLoginExpiredMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APISetIAM2ProjectLoginExpiredMsg.java | +| 74 | `APISetIAM2ProjectRetirePolicyMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APISetIAM2ProjectRetirePolicyMsg.java | +| 75 | `APISetOrganizationOperationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APISetOrganizationOperationMsg.java | +| 76 | `APISetOrganizationSupervisorMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APISetOrganizationSupervisorMsg.java | +| 77 | `APIStopAllResourcesInIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIStopAllResourcesInIAM2ProjectMsg.java | +| 78 | `APIUpdateIAM2OrganizationAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2OrganizationAttributeMsg.java | +| 79 | `APIUpdateIAM2OrganizationMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2OrganizationMsg.java | +| 80 | `APIUpdateIAM2ProjectAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2ProjectAttributeMsg.java | +| 81 | `APIUpdateIAM2ProjectMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2ProjectMsg.java | +| 82 | `APIUpdateIAM2ProjectTemplateMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2ProjectTemplateMsg.java | +| 83 | `APIUpdateIAM2VirtualIDAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2VirtualIDAttributeMsg.java | +| 84 | `APIUpdateIAM2VirtualIDGroupAttributeMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2VirtualIDGroupAttributeMsg.java | +| 85 | `APIUpdateIAM2VirtualIDGroupMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2VirtualIDGroupMsg.java | +| 86 | `APIUpdateIAM2VirtualIDMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateIAM2VirtualIDMsg.java | +| 87 | `APIUpdateOrganizationQuotaMsg` | premium/iam2/src/main/java/org/zstack/iam2/api/APIUpdateOrganizationQuotaMsg.java | + +--- + + + +## 模块:`premium/imagereplicator` + +### `org.zstack.imagereplicator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBackupStoragesToReplicationGroupMsg` | premium/imagereplicator/src/main/java/org/zstack/imagereplicator/APIAddBackupStoragesToReplicationGroupMsg.java | +| 2 | `APICreateImageReplicationGroupMsg` | premium/imagereplicator/src/main/java/org/zstack/imagereplicator/APICreateImageReplicationGroupMsg.java | +| 3 | `APIDeleteImageReplicationGroupMsg` | premium/imagereplicator/src/main/java/org/zstack/imagereplicator/APIDeleteImageReplicationGroupMsg.java | +| 4 | `APIQueryImageReplicationGroupMsg` | premium/imagereplicator/src/main/java/org/zstack/imagereplicator/APIQueryImageReplicationGroupMsg.java | + +--- + + + +## 模块:`premium/log` + +### `org.zstack.log` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddLogConfigurationMsg` | premium/log/src/main/java/org/zstack/log/APIAddLogConfigurationMsg.java | +| 2 | `APIDeleteLogConfigurationMsg` | premium/log/src/main/java/org/zstack/log/APIDeleteLogConfigurationMsg.java | +| 3 | `APIGetLogConfigurationMsg` | premium/log/src/main/java/org/zstack/log/APIGetLogConfigurationMsg.java | +| 4 | `APIUpdateLogConfigurationMsg` | premium/log/src/main/java/org/zstack/log/APIUpdateLogConfigurationMsg.java | + +--- + + + +## 模块:`premium/loginControl` + +### `org.zstack.loginControl.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAccessControlRuleMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIAddAccessControlRuleMsg.java | +| 2 | `APIDeleteAccessControlRuleMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIDeleteAccessControlRuleMsg.java | +| 3 | `APIGetLoginCaptchaMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIGetLoginCaptchaMsg.java | +| 4 | `APIQueryAccessControlRuleMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIQueryAccessControlRuleMsg.java | +| 5 | `APIUnlockIdentityMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIUnlockIdentityMsg.java | +| 6 | `APIUpdateAccessControlRuleMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIUpdateAccessControlRuleMsg.java | +| 7 | `APIValidatePasswordMsg` | premium/loginControl/src/main/java/org/zstack/loginControl/api/APIValidatePasswordMsg.java | + +--- + + + +## 模块:`premium/managements` + +### `org.zstack.managements.api.common` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetManagementNodesStatusMsg` | premium/managements/src/main/java/org/zstack/managements/api/common/APIGetManagementNodesStatusMsg.java | + +### `org.zstack.managements.api.ha2` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetZSha2StatusMsg` | premium/managements/src/main/java/org/zstack/managements/api/ha2/APIGetZSha2StatusMsg.java | +| 2 | `APIZSha2DemoteMsg` | premium/managements/src/main/java/org/zstack/managements/api/ha2/APIZSha2DemoteMsg.java | + +--- + + + +## 模块:`premium/mevoco` + +### `org.zstack.ha` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteVmInstanceHaLevelMsg` | premium/mevoco/src/main/java/org/zstack/ha/APIDeleteVmInstanceHaLevelMsg.java | +| 2 | `APIGetVmInstanceHaLevelMsg` | premium/mevoco/src/main/java/org/zstack/ha/APIGetVmInstanceHaLevelMsg.java | +| 3 | `APISetVmInstanceHaLevelMsg` | premium/mevoco/src/main/java/org/zstack/ha/APISetVmInstanceHaLevelMsg.java | +| 4 | `APIUpdateHaStrategyConditionMsg` | premium/mevoco/src/main/java/org/zstack/ha/APIUpdateHaStrategyConditionMsg.java | + +### `org.zstack.header.affinitygroup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddVmToAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIAddVmToAffinityGroupMsg.java | +| 2 | `APIChangeAffinityGroupStateMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIChangeAffinityGroupStateMsg.java | +| 3 | `APICreateAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APICreateAffinityGroupMsg.java | +| 4 | `APIDeleteAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIDeleteAffinityGroupMsg.java | +| 5 | `APIGetCandidateAffinityGroupForAttachingVmMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIGetCandidateAffinityGroupForAttachingVmMsg.java | +| 6 | `APIGetCandidateAffinityGroupForCreatingVmMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIGetCandidateAffinityGroupForCreatingVmMsg.java | +| 7 | `APIGetCandidateVMForAttachingAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIGetCandidateVMForAttachingAffinityGroupMsg.java | +| 8 | `APIQueryAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIQueryAffinityGroupMsg.java | +| 9 | `APIRemoveVmFromAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIRemoveVmFromAffinityGroupMsg.java | +| 10 | `APIUpdateAffinityGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/affinitygroup/APIUpdateAffinityGroupMsg.java | + +### `org.zstack.header.bonding` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachNicToBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/bonding/APIAttachNicToBondingMsg.java | +| 2 | `APICreateBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/bonding/APICreateBondingMsg.java | +| 3 | `APIDeleteBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/bonding/APIDeleteBondingMsg.java | +| 4 | `APIDetachNicFromBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/bonding/APIDetachNicFromBondingMsg.java | +| 5 | `APIUpdateBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/bonding/APIUpdateBondingMsg.java | + +### `org.zstack.header.bootstrap` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBootstrapMiniHostMsg` | premium/mevoco/src/main/java/org/zstack/header/bootstrap/APIBootstrapMiniHostMsg.java | +| 2 | `APIGetCandidateMiniHostsMsg` | premium/mevoco/src/main/java/org/zstack/header/bootstrap/APIGetCandidateMiniHostsMsg.java | + +### `org.zstack.header.cluster` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateMiniClusterMsg` | premium/mevoco/src/main/java/org/zstack/header/cluster/APICreateMiniClusterMsg.java | + +### `org.zstack.header.host` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddHostFromConfigFileMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIAddHostFromConfigFileMsg.java | +| 2 | `APIAddKVMHostFromConfigFileMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIAddKVMHostFromConfigFileMsg.java | +| 3 | `APIAllocateHostResourceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIAllocateHostResourceMsg.java | +| 4 | `APIChangeHostPasswordMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIChangeHostPasswordMsg.java | +| 5 | `APICheckHostConfigFileMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APICheckHostConfigFileMsg.java | +| 6 | `APICheckKVMHostConfigFileMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APICheckKVMHostConfigFileMsg.java | +| 7 | `APIGetCandidateInterfaceVlanIdsMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetCandidateInterfaceVlanIdsMsg.java | +| 8 | `APIGetCandidateNetworkBondingsMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetCandidateNetworkBondingsMsg.java | +| 9 | `APIGetCandidateNetworkInterfacesMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetCandidateNetworkInterfacesMsg.java | +| 10 | `APIGetClusterHostNetworkFactsMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetClusterHostNetworkFactsMsg.java | +| 11 | `APIGetHostNetworkFactsMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetHostNetworkFactsMsg.java | +| 12 | `APIGetHostNUMATopologyMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetHostNUMATopologyMsg.java | +| 13 | `APIGetHostPhysicalMemoryFactsMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetHostPhysicalMemoryFactsMsg.java | +| 14 | `APIGetHostResourceAllocationMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetHostResourceAllocationMsg.java | +| 15 | `APIGetInterfaceServiceTypeStatisticMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIGetInterfaceServiceTypeStatisticMsg.java | +| 16 | `APIIdentifyHostMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIIdentifyHostMsg.java | +| 17 | `APILocateHostNetworkInterfaceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APILocateHostNetworkInterfaceMsg.java | +| 18 | `APIPowerOffHostMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIPowerOffHostMsg.java | +| 19 | `APIQueryHostNetworkBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIQueryHostNetworkBondingMsg.java | +| 20 | `APIQueryHostNetworkInterfaceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIQueryHostNetworkInterfaceMsg.java | +| 21 | `APIQueryHostPhysicalCpuMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIQueryHostPhysicalCpuMsg.java | +| 22 | `APIQueryHostPhysicalMemoryMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIQueryHostPhysicalMemoryMsg.java | +| 23 | `APISetIpOnHostNetworkBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APISetIpOnHostNetworkBondingMsg.java | +| 24 | `APISetIpOnHostNetworkInterfaceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APISetIpOnHostNetworkInterfaceMsg.java | +| 25 | `APISetServiceTypeOnHostNetworkBondingMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APISetServiceTypeOnHostNetworkBondingMsg.java | +| 26 | `APISetServiceTypeOnHostNetworkInterfaceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APISetServiceTypeOnHostNetworkInterfaceMsg.java | +| 27 | `APIUpdateHostIscsiInitiatorNameMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIUpdateHostIscsiInitiatorNameMsg.java | +| 28 | `APIUpdateHostNetworkInterfaceMsg` | premium/mevoco/src/main/java/org/zstack/header/host/APIUpdateHostNetworkInterfaceMsg.java | + +### `org.zstack.header.image` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetImageQgaMsg` | premium/mevoco/src/main/java/org/zstack/header/image/APIGetImageQgaMsg.java | +| 2 | `APISetImageQgaMsg` | premium/mevoco/src/main/java/org/zstack/header/image/APISetImageQgaMsg.java | +| 3 | `APISetImageSecurityLevelMsg` | premium/mevoco/src/main/java/org/zstack/header/image/APISetImageSecurityLevelMsg.java | + +### `org.zstack.header.managementnode` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetFactoryModeStateMsg` | premium/mevoco/src/main/java/org/zstack/header/managementnode/APIGetFactoryModeStateMsg.java | +| 2 | `APIUpdateFactoryModeStateMsg` | premium/mevoco/src/main/java/org/zstack/header/managementnode/APIUpdateFactoryModeStateMsg.java | + +### `org.zstack.header.securitymachine` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APISecurityMachineEncryptMsg` | premium/mevoco/src/main/java/org/zstack/header/securitymachine/APISecurityMachineEncryptMsg.java | + +### `org.zstack.header.sriov` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeVfNicHaStateMsg` | premium/mevoco/src/main/java/org/zstack/header/sriov/APIChangeVfNicHaStateMsg.java | +| 2 | `APIChangeVmNicTypeMsg` | premium/mevoco/src/main/java/org/zstack/header/sriov/APIChangeVmNicTypeMsg.java | +| 3 | `APIIsVfNicAvailableInL3NetworkMsg` | premium/mevoco/src/main/java/org/zstack/header/sriov/APIIsVfNicAvailableInL3NetworkMsg.java | + +### `org.zstack.header.storage.snapshot` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVolumesSnapshotMsg` | premium/mevoco/src/main/java/org/zstack/header/storage/snapshot/APICreateVolumesSnapshotMsg.java | + +### `org.zstack.header.storageDevice` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachScsiLunToVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIAttachScsiLunToVmInstanceMsg.java | +| 2 | `APICheckScsiLunClusterStatusMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APICheckScsiLunClusterStatusMsg.java | +| 3 | `APIDetachScsiLunFromHostMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIDetachScsiLunFromHostMsg.java | +| 4 | `APIDetachScsiLunFromVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIDetachScsiLunFromVmInstanceMsg.java | +| 5 | `APIGetScsiLunCandidatesForAttachingVmMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIGetScsiLunCandidatesForAttachingVmMsg.java | +| 6 | `APIQueryScsiLunMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIQueryScsiLunMsg.java | +| 7 | `APIUpdateScsiLunMsg` | premium/mevoco/src/main/java/org/zstack/header/storageDevice/APIUpdateScsiLunMsg.java | + +### `org.zstack.header.vipQos` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteVipQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vipQos/APIDeleteVipQosMsg.java | +| 2 | `APIGetVipQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vipQos/APIGetVipQosMsg.java | +| 3 | `APISetVipQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vipQos/APISetVipQosMsg.java | + +### `org.zstack.header.vm` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeVmImageMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIChangeVmImageMsg.java | +| 2 | `APIChangeVmPasswordMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIChangeVmPasswordMsg.java | +| 3 | `APICloneVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APICloneVmInstanceMsg.java | +| 4 | `APICreateTemplatedVmInstanceFromVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APICreateTemplatedVmInstanceFromVmInstanceMsg.java | +| 5 | `APICreateVmInstanceFromTemplatedVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromTemplatedVmInstanceMsg.java | +| 6 | `APIDeleteNicQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIDeleteNicQosMsg.java | +| 7 | `APIDeleteVmUserDefinedXmlHookScriptMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIDeleteVmUserDefinedXmlHookScriptMsg.java | +| 8 | `APIDeleteVmUserDefinedXmlMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIDeleteVmUserDefinedXmlMsg.java | +| 9 | `APIGetImageCandidatesForVmToChangeMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetImageCandidatesForVmToChangeMsg.java | +| 10 | `APIGetNicQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetNicQosMsg.java | +| 11 | `APIGetVirtualizerInfoMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVirtualizerInfoMsg.java | +| 12 | `APIGetVmEmulatorPinningMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmEmulatorPinningMsg.java | +| 13 | `APIGetVmInstanceFirstBootDeviceMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmInstanceFirstBootDeviceMsg.java | +| 14 | `APIGetVmMonitorNumberMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmMonitorNumberMsg.java | +| 15 | `APIGetVmNumaMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmNumaMsg.java | +| 16 | `APIGetVmQgaMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmQgaMsg.java | +| 17 | `APIGetVmRDPMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmRDPMsg.java | +| 18 | `APIGetVmUsbRedirectMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmUsbRedirectMsg.java | +| 19 | `APIGetVmvNUMATopologyMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmvNUMATopologyMsg.java | +| 20 | `APIGetVmXmlHookScriptMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmXmlHookScriptMsg.java | +| 21 | `APIGetVmXmlMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIGetVmXmlMsg.java | +| 22 | `APIQueryVmSchedHistoryMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIQueryVmSchedHistoryMsg.java | +| 23 | `APISetNicQosMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetNicQosMsg.java | +| 24 | `APISetVmCleanTrafficMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmCleanTrafficMsg.java | +| 25 | `APISetVmConsoleModeMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmConsoleModeMsg.java | +| 26 | `APISetVmEmulatorPinningMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmEmulatorPinningMsg.java | +| 27 | `APISetVmMonitorNumberMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmMonitorNumberMsg.java | +| 28 | `APISetVmNumaMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmNumaMsg.java | +| 29 | `APISetVmQgaMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmQgaMsg.java | +| 30 | `APISetVmRDPMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmRDPMsg.java | +| 31 | `APISetVmSecurityLevelMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmSecurityLevelMsg.java | +| 32 | `APISetVmUsbRedirectMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmUsbRedirectMsg.java | +| 33 | `APISetVmUserDefinedXmlHookScriptMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmUserDefinedXmlHookScriptMsg.java | +| 34 | `APISetVmUserDefinedXmlMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISetVmUserDefinedXmlMsg.java | +| 35 | `APISyncVmClockMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APISyncVmClockMsg.java | +| 36 | `APIUpdateVmNicMacMsg` | premium/mevoco/src/main/java/org/zstack/header/vm/APIUpdateVmNicMacMsg.java | + +### `org.zstack.header.vmscheduling` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddHostToHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIAddHostToHostSchedulingRuleGroupMsg.java | +| 2 | `APIAddVmToVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIAddVmToVmSchedulingRuleGroupMsg.java | +| 3 | `APIChangeVmSchedulingRuleStateMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIChangeVmSchedulingRuleStateMsg.java | +| 4 | `APICreateHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APICreateHostSchedulingRuleGroupMsg.java | +| 5 | `APICreateVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APICreateVmSchedulingRuleGroupMsg.java | +| 6 | `APICreateVmSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APICreateVmSchedulingRuleMsg.java | +| 7 | `APIDeleteHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIDeleteHostSchedulingRuleGroupMsg.java | +| 8 | `APIDeleteVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIDeleteVmSchedulingRuleGroupMsg.java | +| 9 | `APIDetachHostFromHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIDetachHostFromHostSchedulingRuleGroupMsg.java | +| 10 | `APIDetachVmFromVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIDetachVmFromVmSchedulingRuleGroupMsg.java | +| 11 | `APIGetVmSchedulingRulesExecuteStateMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIGetVmSchedulingRulesExecuteStateMsg.java | +| 12 | `APIGetVmsSchedulingStateFromSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIGetVmsSchedulingStateFromSchedulingRuleMsg.java | +| 13 | `APIListVmSchedulingRulesFromExecuteStateMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIListVmSchedulingRulesFromExecuteStateMsg.java | +| 14 | `APIListVmsFromSchedulingStateMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIListVmsFromSchedulingStateMsg.java | +| 15 | `APIQueryHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIQueryHostSchedulingRuleGroupMsg.java | +| 16 | `APIQueryVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIQueryVmSchedulingRuleGroupMsg.java | +| 17 | `APIQueryVmSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIQueryVmSchedulingRuleMsg.java | +| 18 | `APIRemoveVmSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIRemoveVmSchedulingRuleMsg.java | +| 19 | `APIUpdateHostSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIUpdateHostSchedulingRuleGroupMsg.java | +| 20 | `APIUpdateVmSchedulingRuleGroupMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIUpdateVmSchedulingRuleGroupMsg.java | +| 21 | `APIUpdateVmSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIUpdateVmSchedulingRuleMsg.java | +| 22 | `APIValidateVmSchedulingRuleMsg` | premium/mevoco/src/main/java/org/zstack/header/vmscheduling/APIValidateVmSchedulingRuleMsg.java | + +### `org.zstack.header.volume` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteVolumeQosMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIDeleteVolumeQosMsg.java | +| 2 | `APIGetVolumeIoThreadPinMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIGetVolumeIoThreadPinMsg.java | +| 3 | `APIGetVolumeQosMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIGetVolumeQosMsg.java | +| 4 | `APIResizeDataVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIResizeDataVolumeMsg.java | +| 5 | `APIResizeRootVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIResizeRootVolumeMsg.java | +| 6 | `APISetVolumeIoThreadPinMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APISetVolumeIoThreadPinMsg.java | +| 7 | `APISetVolumeQosMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APISetVolumeQosMsg.java | +| 8 | `APIValidateVolumeSnapshotChainMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/APIValidateVolumeSnapshotChainMsg.java | + +### `org.zstack.header.volume.block` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APICreateBlockVolumeMsg.java | +| 2 | `APIGetAccessPathMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIGetAccessPathMsg.java | +| 3 | `APIQueryBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIQueryBlockVolumeMsg.java | +| 4 | `APIQueryExponBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIQueryExponBlockVolumeMsg.java | +| 5 | `APIQueryXskyBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIQueryXskyBlockVolumeMsg.java | +| 6 | `APIUpdateBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIUpdateBlockVolumeMsg.java | +| 7 | `APIUpdateXskyBlockVolumeMsg` | premium/mevoco/src/main/java/org/zstack/header/volume/block/APIUpdateXskyBlockVolumeMsg.java | + +### `org.zstack.license` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteLicenseMsg` | premium/mevoco/src/main/java/org/zstack/license/APIDeleteLicenseMsg.java | +| 2 | `APIGetLicenseAddOnsMsg` | premium/mevoco/src/main/java/org/zstack/license/APIGetLicenseAddOnsMsg.java | +| 3 | `APIGetLicenseCapabilitiesMsg` | premium/mevoco/src/main/java/org/zstack/license/APIGetLicenseCapabilitiesMsg.java | +| 4 | `APIGetLicenseInfoMsg` | premium/mevoco/src/main/java/org/zstack/license/APIGetLicenseInfoMsg.java | +| 5 | `APIGetLicenseRecordsMsg` | premium/mevoco/src/main/java/org/zstack/license/APIGetLicenseRecordsMsg.java | +| 6 | `APIGetLicenseUKeyStatusMsg` | premium/mevoco/src/main/java/org/zstack/license/APIGetLicenseUKeyStatusMsg.java | +| 7 | `APIRegisterLicenseRequestedApplicationMsg` | premium/mevoco/src/main/java/org/zstack/license/APIRegisterLicenseRequestedApplicationMsg.java | +| 8 | `APIReloadLicenseMsg` | premium/mevoco/src/main/java/org/zstack/license/APIReloadLicenseMsg.java | +| 9 | `APIUpdateLicenseMsg` | premium/mevoco/src/main/java/org/zstack/license/APIUpdateLicenseMsg.java | + +### `org.zstack.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIReplayMessageMsg` | premium/mevoco/src/main/java/org/zstack/message/APIReplayMessageMsg.java | + +### `org.zstack.mevoco` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryShareableVolumeVmInstanceRefMsg` | premium/mevoco/src/main/java/org/zstack/mevoco/APIQueryShareableVolumeVmInstanceRefMsg.java | + +### `org.zstack.monitoring` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachMonitorTriggerActionToTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIAttachMonitorTriggerActionToTriggerMsg.java | +| 2 | `APIChangeMonitorTriggerStateMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIChangeMonitorTriggerStateMsg.java | +| 3 | `APICreateMonitorTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APICreateMonitorTriggerMsg.java | +| 4 | `APIDeleteAlertMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIDeleteAlertMsg.java | +| 5 | `APIDeleteMonitorTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIDeleteMonitorTriggerMsg.java | +| 6 | `APIDetachMonitorTriggerActionFromTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIDetachMonitorTriggerActionFromTriggerMsg.java | +| 7 | `APIGetMonitorItemMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIGetMonitorItemMsg.java | +| 8 | `APIQueryAlertMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIQueryAlertMsg.java | +| 9 | `APIQueryMonitorTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIQueryMonitorTriggerMsg.java | +| 10 | `APIUpdateMonitorTriggerMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/APIUpdateMonitorTriggerMsg.java | + +### `org.zstack.monitoring.actions` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeMonitorTriggerActionStateMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APIChangeMonitorTriggerActionStateMsg.java | +| 2 | `APICreateEmailMonitorTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APICreateEmailMonitorTriggerActionMsg.java | +| 3 | `APICreateMonitorTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APICreateMonitorTriggerActionMsg.java | +| 4 | `APIDeleteMonitorTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APIDeleteMonitorTriggerActionMsg.java | +| 5 | `APIQueryEmailTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APIQueryEmailTriggerActionMsg.java | +| 6 | `APIQueryMonitorTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APIQueryMonitorTriggerActionMsg.java | +| 7 | `APIUpdateEmailMonitorTriggerActionMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/actions/APIUpdateEmailMonitorTriggerActionMsg.java | + +### `org.zstack.monitoring.media` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeMediaStateMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APIChangeMediaStateMsg.java | +| 2 | `APICreateEmailMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APICreateEmailMediaMsg.java | +| 3 | `APICreateMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APICreateMediaMsg.java | +| 4 | `APIDeleteMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APIDeleteMediaMsg.java | +| 5 | `APIQueryEmailMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APIQueryEmailMediaMsg.java | +| 6 | `APIQueryMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APIQueryMediaMsg.java | +| 7 | `APIUpdateEmailMediaMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/media/APIUpdateEmailMediaMsg.java | + +### `org.zstack.monitoring.prometheus` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIPrometheusQueryLabelValuesMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/prometheus/APIPrometheusQueryLabelValuesMsg.java | +| 2 | `APIPrometheusQueryMetadataMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/prometheus/APIPrometheusQueryMetadataMsg.java | +| 3 | `APIPrometheusQueryMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/prometheus/APIPrometheusQueryMsg.java | +| 4 | `APIPrometheusQueryPassThroughMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/prometheus/APIPrometheusQueryPassThroughMsg.java | +| 5 | `APIPrometheusQueryVmMonitoringDataMsg` | premium/mevoco/src/main/java/org/zstack/monitoring/prometheus/APIPrometheusQueryVmMonitoringDataMsg.java | + +### `org.zstack.mttyDevice` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGenerateSeMdevDevicesMsg` | premium/mevoco/src/main/java/org/zstack/mttyDevice/APIGenerateSeMdevDevicesMsg.java | +| 2 | `APIQueryMttyDeviceMsg` | premium/mevoco/src/main/java/org/zstack/mttyDevice/APIQueryMttyDeviceMsg.java | +| 3 | `APIUngenerateSeMdevDevicesMsg` | premium/mevoco/src/main/java/org/zstack/mttyDevice/APIUngenerateSeMdevDevicesMsg.java | + +### `org.zstack.nas` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateNasFileSystemMsg` | premium/mevoco/src/main/java/org/zstack/nas/APICreateNasFileSystemMsg.java | +| 2 | `APICreateNasMountTargetMsg` | premium/mevoco/src/main/java/org/zstack/nas/APICreateNasMountTargetMsg.java | +| 3 | `APIDeleteNasFileSystemMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIDeleteNasFileSystemMsg.java | +| 4 | `APIDeleteNasMountTargetMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIDeleteNasMountTargetMsg.java | +| 5 | `APIQueryNasFileSystemMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIQueryNasFileSystemMsg.java | +| 6 | `APIQueryNasMountTargetMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIQueryNasMountTargetMsg.java | +| 7 | `APIUpdateNasFileSystemMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIUpdateNasFileSystemMsg.java | +| 8 | `APIUpdateNasMountTargetMsg` | premium/mevoco/src/main/java/org/zstack/nas/APIUpdateNasMountTargetMsg.java | + +### `org.zstack.pciDevice` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachPciDeviceToVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIAttachPciDeviceToVmMsg.java | +| 2 | `APICreatePciDeviceOfferingMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APICreatePciDeviceOfferingMsg.java | +| 3 | `APIDeletePciDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIDeletePciDeviceMsg.java | +| 4 | `APIDeletePciDeviceOfferingMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIDeletePciDeviceOfferingMsg.java | +| 5 | `APIDetachPciDeviceFromVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIDetachPciDeviceFromVmMsg.java | +| 6 | `APIGetHostIommuStateMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIGetHostIommuStateMsg.java | +| 7 | `APIGetHostIommuStatusMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIGetHostIommuStatusMsg.java | +| 8 | `APIGetPciDeviceCandidatesForAttachingVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIGetPciDeviceCandidatesForAttachingVmMsg.java | +| 9 | `APIGetPciDeviceCandidatesForNewCreateVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIGetPciDeviceCandidatesForNewCreateVmMsg.java | +| 10 | `APIQueryPciDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIQueryPciDeviceMsg.java | +| 11 | `APIQueryPciDeviceOfferingMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIQueryPciDeviceOfferingMsg.java | +| 12 | `APIQueryPciDevicePciDeviceOfferingMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIQueryPciDevicePciDeviceOfferingMsg.java | +| 13 | `APIUpdateHostIommuStateMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIUpdateHostIommuStateMsg.java | +| 14 | `APIUpdatePciDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/APIUpdatePciDeviceMsg.java | + +### `org.zstack.pciDevice.gpu` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryGpuDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/gpu/APIQueryGpuDeviceMsg.java | + +### `org.zstack.pciDevice.specification.mdev` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddMdevDeviceSpecToVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIAddMdevDeviceSpecToVmInstanceMsg.java | +| 2 | `APIGetMdevDeviceSpecCandidatesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIGetMdevDeviceSpecCandidatesMsg.java | +| 3 | `APIQueryMdevDeviceSpecMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIQueryMdevDeviceSpecMsg.java | +| 4 | `APIQueryVmInstanceMdevDeviceSpecRefMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIQueryVmInstanceMdevDeviceSpecRefMsg.java | +| 5 | `APIRemoveMdevDeviceSpecFromVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIRemoveMdevDeviceSpecFromVmInstanceMsg.java | +| 6 | `APIUpdateMdevDeviceSpecMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/mdev/APIUpdateMdevDeviceSpecMsg.java | + +### `org.zstack.pciDevice.specification.pci` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddPciDeviceSpecToVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIAddPciDeviceSpecToVmInstanceMsg.java | +| 2 | `APIGetPciDeviceSpecCandidatesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIGetPciDeviceSpecCandidatesMsg.java | +| 3 | `APIQueryPciDeviceSpecMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIQueryPciDeviceSpecMsg.java | +| 4 | `APIQueryVmInstancePciDeviceSpecRefMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIQueryVmInstancePciDeviceSpecRefMsg.java | +| 5 | `APIRemovePciDeviceSpecFromVmInstanceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIRemovePciDeviceSpecFromVmInstanceMsg.java | +| 6 | `APIUpdatePciDeviceSpecMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/specification/pci/APIUpdatePciDeviceSpecMsg.java | + +### `org.zstack.pciDevice.virtual` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGenerateVirtualPciDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/APIGenerateVirtualPciDevicesMsg.java | +| 2 | `APIUngenerateVirtualPciDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/APIUngenerateVirtualPciDevicesMsg.java | + +### `org.zstack.pciDevice.virtual.sr_iov` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGenerateSriovPciDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/sr_iov/APIGenerateSriovPciDevicesMsg.java | +| 2 | `APIQueryEthernetVFMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/sr_iov/APIQueryEthernetVFMsg.java | +| 3 | `APIUngenerateSriovPciDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/sr_iov/APIUngenerateSriovPciDevicesMsg.java | + +### `org.zstack.pciDevice.virtual.vfio_mdev` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachMdevDeviceToVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIAttachMdevDeviceToVmMsg.java | +| 2 | `APIDeleteMdevDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIDeleteMdevDeviceMsg.java | +| 3 | `APIDetachMdevDeviceFromVmMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIDetachMdevDeviceFromVmMsg.java | +| 4 | `APIGenerateMdevDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIGenerateMdevDevicesMsg.java | +| 5 | `APIGetMdevDeviceCandidatesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIGetMdevDeviceCandidatesMsg.java | +| 6 | `APIQueryMdevDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIQueryMdevDeviceMsg.java | +| 7 | `APIUngenerateMdevDevicesMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIUngenerateMdevDevicesMsg.java | +| 8 | `APIUpdateMdevDeviceMsg` | premium/mevoco/src/main/java/org/zstack/pciDevice/virtual/vfio_mdev/APIUpdateMdevDeviceMsg.java | + +### `org.zstack.scheduler` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSchedulerJobGroupToSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIAddSchedulerJobGroupToSchedulerTriggerMsg.java | +| 2 | `APIAddSchedulerJobsToSchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIAddSchedulerJobsToSchedulerJobGroupMsg.java | +| 3 | `APIAddSchedulerJobToSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIAddSchedulerJobToSchedulerTriggerMsg.java | +| 4 | `APIChangeSchedulerStateMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIChangeSchedulerStateMsg.java | +| 5 | `APICreateSchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APICreateSchedulerJobGroupMsg.java | +| 6 | `APICreateSchedulerJobMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APICreateSchedulerJobMsg.java | +| 7 | `APICreateSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APICreateSchedulerTriggerMsg.java | +| 8 | `APIDeleteSchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIDeleteSchedulerJobGroupMsg.java | +| 9 | `APIDeleteSchedulerJobMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIDeleteSchedulerJobMsg.java | +| 10 | `APIDeleteSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIDeleteSchedulerTriggerMsg.java | +| 11 | `APIGetAvailableTriggersMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIGetAvailableTriggersMsg.java | +| 12 | `APIGetNoTriggerSchedulerJobsMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIGetNoTriggerSchedulerJobsMsg.java | +| 13 | `APIGetSchedulerExecutionReportMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIGetSchedulerExecutionReportMsg.java | +| 14 | `APIQuerySchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIQuerySchedulerJobGroupMsg.java | +| 15 | `APIQuerySchedulerJobHistoryMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIQuerySchedulerJobHistoryMsg.java | +| 16 | `APIQuerySchedulerJobMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIQuerySchedulerJobMsg.java | +| 17 | `APIQuerySchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIQuerySchedulerTriggerMsg.java | +| 18 | `APIRemoveSchedulerJobFromSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIRemoveSchedulerJobFromSchedulerTriggerMsg.java | +| 19 | `APIRemoveSchedulerJobGroupFromSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIRemoveSchedulerJobGroupFromSchedulerTriggerMsg.java | +| 20 | `APIRemoveSchedulerJobsFromSchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIRemoveSchedulerJobsFromSchedulerJobGroupMsg.java | +| 21 | `APIRunSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIRunSchedulerTriggerMsg.java | +| 22 | `APIUpdateSchedulerJobGroupMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIUpdateSchedulerJobGroupMsg.java | +| 23 | `APIUpdateSchedulerJobMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIUpdateSchedulerJobMsg.java | +| 24 | `APIUpdateSchedulerTriggerMsg` | premium/mevoco/src/main/java/org/zstack/scheduler/APIUpdateSchedulerTriggerMsg.java | + +### `org.zstack.storage.backup.imagestore` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddDisasterImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIAddDisasterImageStoreBackupStorageMsg.java | +| 2 | `APIAddImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIAddImageStoreBackupStorageMsg.java | +| 3 | `APIGetImagesFromImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIGetImagesFromImageStoreBackupStorageMsg.java | +| 4 | `APIQueryImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIQueryImageStoreBackupStorageMsg.java | +| 5 | `APIReclaimSpaceFromImageStoreMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIReclaimSpaceFromImageStoreMsg.java | +| 6 | `APIReconnectImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIReconnectImageStoreBackupStorageMsg.java | +| 7 | `APIRecoveryImageFromImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIRecoveryImageFromImageStoreBackupStorageMsg.java | +| 8 | `APISetImageStoreBackupStorageQuotaMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APISetImageStoreBackupStorageQuotaMsg.java | +| 9 | `APISyncImageFromImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APISyncImageFromImageStoreBackupStorageMsg.java | +| 10 | `APISyncImageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APISyncImageMsg.java | +| 11 | `APIUpdateImageStoreBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/storage/backup/imagestore/APIUpdateImageStoreBackupStorageMsg.java | + +### `org.zstack.storage.migration.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBackupStorageMigrateImageMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/backup/APIBackupStorageMigrateImageMsg.java | +| 2 | `APIGetBackupStorageCandidatesForImageMigrationMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/backup/APIGetBackupStorageCandidatesForImageMigrationMsg.java | + +### `org.zstack.storage.migration.primary` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetHostCandidatesForVmMigrationMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/primary/APIGetHostCandidatesForVmMigrationMsg.java | +| 2 | `APIGetPrimaryStorageCandidatesForVmMigrationMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/primary/APIGetPrimaryStorageCandidatesForVmMigrationMsg.java | +| 3 | `APIGetPrimaryStorageCandidatesForVolumeMigrationMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/primary/APIGetPrimaryStorageCandidatesForVolumeMigrationMsg.java | +| 4 | `APIPrimaryStorageMigrateVmMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/primary/APIPrimaryStorageMigrateVmMsg.java | +| 5 | `APIPrimaryStorageMigrateVolumeMsg` | premium/mevoco/src/main/java/org/zstack/storage/migration/primary/APIPrimaryStorageMigrateVolumeMsg.java | + +### `org.zstack.templateConfig` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIApplyTemplateConfigMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIApplyTemplateConfigMsg.java | +| 2 | `APIQueryGlobalConfigTemplateMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIQueryGlobalConfigTemplateMsg.java | +| 3 | `APIQueryTemplateConfigMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIQueryTemplateConfigMsg.java | +| 4 | `APIResetTemplateConfigMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIResetTemplateConfigMsg.java | +| 5 | `APIRevertTemplateConfigMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIRevertTemplateConfigMsg.java | +| 6 | `APIUpdateTemplateConfigMsg` | premium/mevoco/src/main/java/org/zstack/templateConfig/APIUpdateTemplateConfigMsg.java | + +### `org.zstack.usbDevice` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachUsbDeviceToVmMsg` | premium/mevoco/src/main/java/org/zstack/usbDevice/APIAttachUsbDeviceToVmMsg.java | +| 2 | `APIDetachUsbDeviceFromVmMsg` | premium/mevoco/src/main/java/org/zstack/usbDevice/APIDetachUsbDeviceFromVmMsg.java | +| 3 | `APIGetUsbDeviceCandidatesForAttachingVmMsg` | premium/mevoco/src/main/java/org/zstack/usbDevice/APIGetUsbDeviceCandidatesForAttachingVmMsg.java | +| 4 | `APIQueryUsbDeviceMsg` | premium/mevoco/src/main/java/org/zstack/usbDevice/APIQueryUsbDeviceMsg.java | +| 5 | `APIUpdateUsbDeviceMsg` | premium/mevoco/src/main/java/org/zstack/usbDevice/APIUpdateUsbDeviceMsg.java | + +### `org.zstack.vmware` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddVCenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIAddVCenterMsg.java | +| 2 | `APIDeleteVCenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIDeleteVCenterMsg.java | +| 3 | `APIGetVCenterDVSwitchesMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIGetVCenterDVSwitchesMsg.java | +| 4 | `APIQueryVCenterBackupStorageMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterBackupStorageMsg.java | +| 5 | `APIQueryVCenterClusterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterClusterMsg.java | +| 6 | `APIQueryVCenterDatacenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterDatacenterMsg.java | +| 7 | `APIQueryVCenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterMsg.java | +| 8 | `APIQueryVCenterPrimaryStorageMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterPrimaryStorageMsg.java | +| 9 | `APIQueryVCenterResourcePoolMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIQueryVCenterResourcePoolMsg.java | +| 10 | `APISyncVCenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APISyncVCenterMsg.java | +| 11 | `APIUpdateVCenterMsg` | premium/mevoco/src/main/java/org/zstack/vmware/APIUpdateVCenterMsg.java | + +### `org.zstack.vrouterRoute` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddVRouterRouteEntryMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIAddVRouterRouteEntryMsg.java | +| 2 | `APIAttachVRouterRouteTableToVRouterMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIAttachVRouterRouteTableToVRouterMsg.java | +| 3 | `APICreateVRouterRouteTableMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APICreateVRouterRouteTableMsg.java | +| 4 | `APIDeleteVRouterRouteEntryMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIDeleteVRouterRouteEntryMsg.java | +| 5 | `APIDeleteVRouterRouteTableMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIDeleteVRouterRouteTableMsg.java | +| 6 | `APIDetachVRouterRouteTableFromVRouterMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIDetachVRouterRouteTableFromVRouterMsg.java | +| 7 | `APIGetVRouterRouteTableMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIGetVRouterRouteTableMsg.java | +| 8 | `APIQueryVirtualRouterVRouterRouteTableRefMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIQueryVirtualRouterVRouterRouteTableRefMsg.java | +| 9 | `APIQueryVRouterRouteEntryMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIQueryVRouterRouteEntryMsg.java | +| 10 | `APIQueryVRouterRouteTableMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIQueryVRouterRouteTableMsg.java | +| 11 | `APIUpdateVRouterRouteTableMsg` | premium/mevoco/src/main/java/org/zstack/vrouterRoute/APIUpdateVRouterRouteTableMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/aliyunproxy` + +### `org.zstack.aliyunproxy.vpc` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAliyunProxyVpcMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APICreateAliyunProxyVpcMsg.java | +| 2 | `APICreateAliyunProxyVSwitchMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APICreateAliyunProxyVSwitchMsg.java | +| 3 | `APIDeleteAliyunProxyVpcMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIDeleteAliyunProxyVpcMsg.java | +| 4 | `APIDeleteAliyunProxyVSwitchMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIDeleteAliyunProxyVSwitchMsg.java | +| 5 | `APIQueryAliyunProxyVpcMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIQueryAliyunProxyVpcMsg.java | +| 6 | `APIQueryAliyunProxyVSwitchMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIQueryAliyunProxyVSwitchMsg.java | +| 7 | `APIUpdateAliyunProxyVpcMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIUpdateAliyunProxyVpcMsg.java | +| 8 | `APIUpdateAliyunProxyVSwitchMsg` | premium/plugin-premium/aliyunproxy/src/main/java/org/zstack/aliyunproxy/vpc/APIUpdateAliyunProxyVSwitchMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/aliyun-storage` + +### `org.zstack.aliyun.ebs.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAliyunEbsBackupStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIAddAliyunEbsBackupStorageMsg.java | +| 2 | `APIAddAliyunEbsPrimaryStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIAddAliyunEbsPrimaryStorageMsg.java | +| 3 | `APIQueryAliyunEbsBackupStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIQueryAliyunEbsBackupStorageMsg.java | +| 4 | `APIQueryAliyunEbsPrimaryStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIQueryAliyunEbsPrimaryStorageMsg.java | +| 5 | `APIUpdateAliyunEbsBackupStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIUpdateAliyunEbsBackupStorageMsg.java | +| 6 | `APIUpdateAliyunEbsPrimaryStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/ebs/message/APIUpdateAliyunEbsPrimaryStorageMsg.java | + +### `org.zstack.aliyun.nas.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAliyunNasAccessGroupMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIAddAliyunNasAccessGroupMsg.java | +| 2 | `APIAddAliyunNasFileSystemMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIAddAliyunNasFileSystemMsg.java | +| 3 | `APIAddAliyunNasMountTargetMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIAddAliyunNasMountTargetMsg.java | +| 4 | `APICreateAliyunNasAccessGroupMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APICreateAliyunNasAccessGroupMsg.java | +| 5 | `APICreateAliyunNasAccessGroupRuleMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APICreateAliyunNasAccessGroupRuleMsg.java | +| 6 | `APICreateAliyunNasFileSystemMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APICreateAliyunNasFileSystemMsg.java | +| 7 | `APICreateAliyunNasMountTargetMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APICreateAliyunNasMountTargetMsg.java | +| 8 | `APIDeleteAliyunNasAccessGroupMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIDeleteAliyunNasAccessGroupMsg.java | +| 9 | `APIDeleteAliyunNasAccessGroupRuleMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIDeleteAliyunNasAccessGroupRuleMsg.java | +| 10 | `APIGetAliyunNasAccessGroupRemoteMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIGetAliyunNasAccessGroupRemoteMsg.java | +| 11 | `APIGetAliyunNasFileSystemRemoteMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIGetAliyunNasFileSystemRemoteMsg.java | +| 12 | `APIGetAliyunNasMountTargetRemoteMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIGetAliyunNasMountTargetRemoteMsg.java | +| 13 | `APIQueryAliyunNasAccessGroupMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIQueryAliyunNasAccessGroupMsg.java | +| 14 | `APIUpdateAliyunMountTargetMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIUpdateAliyunMountTargetMsg.java | +| 15 | `APIUpdateAliyunNasAccessGroupMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/message/APIUpdateAliyunNasAccessGroupMsg.java | + +### `org.zstack.aliyun.nas.storage.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAliyunNasPrimaryStorageMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/nas/storage/message/APIAddAliyunNasPrimaryStorageMsg.java | + +### `org.zstack.aliyun.pangu` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddAliyunPanguPartitionMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/pangu/APIAddAliyunPanguPartitionMsg.java | +| 2 | `APIDeleteAliyunPanguPartitionMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/pangu/APIDeleteAliyunPanguPartitionMsg.java | +| 3 | `APIQueryAliyunPanguPartitionMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/pangu/APIQueryAliyunPanguPartitionMsg.java | +| 4 | `APIUpdateAliyunPanguPartitionMsg` | premium/plugin-premium/aliyun-storage/src/main/java/org/zstack/aliyun/pangu/APIUpdateAliyunPanguPartitionMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/block-primary-storage` + +### `org.zstack.storage.primary.block.message` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddBlockPrimaryStorageMsg` | premium/plugin-premium/block-primary-storage/src/main/java/org/zstack/storage/primary/block/message/APIAddBlockPrimaryStorageMsg.java | +| 2 | `APIGetBlockPrimaryStorageMetadataMsg` | premium/plugin-premium/block-primary-storage/src/main/java/org/zstack/storage/primary/block/message/APIGetBlockPrimaryStorageMetadataMsg.java | +| 3 | `APIQueryBlockPrimaryStorageMsg` | premium/plugin-premium/block-primary-storage/src/main/java/org/zstack/storage/primary/block/message/APIQueryBlockPrimaryStorageMsg.java | +| 4 | `APIUpdateBlockPrimaryStorageMsg` | premium/plugin-premium/block-primary-storage/src/main/java/org/zstack/storage/primary/block/message/APIUpdateBlockPrimaryStorageMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/daho` + +### `org.zstack.header.daho.process` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateDahoVllRemoteMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APICreateDahoVllRemoteMsg.java | +| 2 | `APIDeleteDahoCloudConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIDeleteDahoCloudConnectionMsg.java | +| 3 | `APIDeleteDahoDataCenterConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIDeleteDahoDataCenterConnectionMsg.java | +| 4 | `APIDeleteDahoVllMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIDeleteDahoVllMsg.java | +| 5 | `APIQueryDahoCloudConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIQueryDahoCloudConnectionMsg.java | +| 6 | `APIQueryDahoDataCenterConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIQueryDahoDataCenterConnectionMsg.java | +| 7 | `APIQueryDahoVllMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIQueryDahoVllMsg.java | +| 8 | `APIQueryDahoVllVbrRefMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIQueryDahoVllVbrRefMsg.java | +| 9 | `APISyncDahoCloudConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APISyncDahoCloudConnectionMsg.java | +| 10 | `APISyncDahoDataCenterConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APISyncDahoDataCenterConnectionMsg.java | +| 11 | `APISyncDahoVllMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APISyncDahoVllMsg.java | +| 12 | `APIUpdateDahoCloudConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIUpdateDahoCloudConnectionMsg.java | +| 13 | `APIUpdateDahoDataCenterConnectionMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIUpdateDahoDataCenterConnectionMsg.java | +| 14 | `APIUpdateDahoVllMsg` | premium/plugin-premium/daho/src/main/java/org/zstack/header/daho/process/APIUpdateDahoVllMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/flowMeter` + +### `org.zstack.header.flowMeter` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddVRouterNetworksToFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIAddVRouterNetworksToFlowMeterMsg.java | +| 2 | `APICreateFlowCollectorMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APICreateFlowCollectorMsg.java | +| 3 | `APICreateFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APICreateFlowMeterMsg.java | +| 4 | `APIDeleteFlowCollectorMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIDeleteFlowCollectorMsg.java | +| 5 | `APIDeleteFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIDeleteFlowMeterMsg.java | +| 6 | `APIGetFlowMeterRouterIdMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIGetFlowMeterRouterIdMsg.java | +| 7 | `APIGetVpcAttachedNetflowMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIGetVpcAttachedNetflowMsg.java | +| 8 | `APIGetVRouterFlowCounterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIGetVRouterFlowCounterMsg.java | +| 9 | `APIQueryFlowCollectorMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIQueryFlowCollectorMsg.java | +| 10 | `APIQueryFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIQueryFlowMeterMsg.java | +| 11 | `APIQueryVRouterFlowMeterNetworkMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIQueryVRouterFlowMeterNetworkMsg.java | +| 12 | `APIRemoveVRouterNetworksFromFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIRemoveVRouterNetworksFromFlowMeterMsg.java | +| 13 | `APISetFlowMeterRouterIdMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APISetFlowMeterRouterIdMsg.java | +| 14 | `APIUpdateFlowCollectorMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIUpdateFlowCollectorMsg.java | +| 15 | `APIUpdateFlowMeterMsg` | premium/plugin-premium/flowMeter/src/main/java/org/zstack/header/flowMeter/APIUpdateFlowMeterMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/ministorage` + +### `org.zstack.storage.primary.ministorage` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddMiniStorageMsg` | premium/plugin-premium/ministorage/src/main/java/org/zstack/storage/primary/ministorage/APIAddMiniStorageMsg.java | +| 2 | `APIQueryMiniStorageHostRefMsg` | premium/plugin-premium/ministorage/src/main/java/org/zstack/storage/primary/ministorage/APIQueryMiniStorageHostRefMsg.java | +| 3 | `APIQueryMiniStorageMsg` | premium/plugin-premium/ministorage/src/main/java/org/zstack/storage/primary/ministorage/APIQueryMiniStorageMsg.java | +| 4 | `APIQueryMiniStorageResourceReplicationMsg` | premium/plugin-premium/ministorage/src/main/java/org/zstack/storage/primary/ministorage/APIQueryMiniStorageResourceReplicationMsg.java | +| 5 | `APIRecoverResourceSplitBrainMsg` | premium/plugin-premium/ministorage/src/main/java/org/zstack/storage/primary/ministorage/APIRecoverResourceSplitBrainMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/multicast-router` + +### `org.zstack.multicast.router.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddRendezvousPointToMulticastRouterMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIAddRendezvousPointToMulticastRouterMsg.java | +| 2 | `APIChangeMulticastRouterStateMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIChangeMulticastRouterStateMsg.java | +| 3 | `APICreateMulticastRouterMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APICreateMulticastRouterMsg.java | +| 4 | `APIDeleteMulticastRouterMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIDeleteMulticastRouterMsg.java | +| 5 | `APIGetVpcMulticastRouteMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIGetVpcMulticastRouteMsg.java | +| 6 | `APIQueryMulticastRouterMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIQueryMulticastRouterMsg.java | +| 7 | `APIRemoveRendezvousPointFromMulticastRouterMsg` | premium/plugin-premium/multicast-router/src/main/java/org/zstack/multicast/router/header/APIRemoveRendezvousPointFromMulticastRouterMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/ovf` + +### `org.zstack.ovf.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateVmInstanceFromOvfMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APICreateVmInstanceFromOvfMsg.java | +| 2 | `APIDeleteImagePackageMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APIDeleteImagePackageMsg.java | +| 3 | `APIExportVmOvaPackageMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APIExportVmOvaPackageMsg.java | +| 4 | `APIParseOvfMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APIParseOvfMsg.java | +| 5 | `APIQueryImagePackageMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APIQueryImagePackageMsg.java | +| 6 | `APIUpdateImagePackageMsg` | premium/plugin-premium/ovf/src/main/java/org/zstack/ovf/api/APIUpdateImagePackageMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/policyRoute` + +### `org.zstack.policyRoute` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachPolicyRouteRuleSetToL3Msg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIAttachPolicyRouteRuleSetToL3Msg.java | +| 2 | `APICreatePolicyRouteRuleMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APICreatePolicyRouteRuleMsg.java | +| 3 | `APICreatePolicyRouteRuleSetMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APICreatePolicyRouteRuleSetMsg.java | +| 4 | `APICreatePolicyRouteTableMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APICreatePolicyRouteTableMsg.java | +| 5 | `APICreatePolicyRouteTableRouteEntryMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APICreatePolicyRouteTableRouteEntryMsg.java | +| 6 | `APIDeletePolicyRouteRuleMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIDeletePolicyRouteRuleMsg.java | +| 7 | `APIDeletePolicyRouteRuleSetMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIDeletePolicyRouteRuleSetMsg.java | +| 8 | `APIDeletePolicyRouteTableMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIDeletePolicyRouteTableMsg.java | +| 9 | `APIDeletePolicyRouteTableRouteEntryMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIDeletePolicyRouteTableRouteEntryMsg.java | +| 10 | `APIDetachPolicyRouteRuleSetFromL3Msg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIDetachPolicyRouteRuleSetFromL3Msg.java | +| 11 | `APIGetPolicyRouteRuleSetFromVirtualRouterMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIGetPolicyRouteRuleSetFromVirtualRouterMsg.java | +| 12 | `APIQueryPolicyRouteRuleMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteRuleMsg.java | +| 13 | `APIQueryPolicyRouteRuleSetL3RefMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteRuleSetL3RefMsg.java | +| 14 | `APIQueryPolicyRouteRuleSetMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteRuleSetMsg.java | +| 15 | `APIQueryPolicyRouteRuleSetVRouterRefMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteRuleSetVRouterRefMsg.java | +| 16 | `APIQueryPolicyRouteTableMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteTableMsg.java | +| 17 | `APIQueryPolicyRouteTableRouteEntryMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteTableRouteEntryMsg.java | +| 18 | `APIQueryPolicyRouteTableVRouterRefMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIQueryPolicyRouteTableVRouterRefMsg.java | +| 19 | `APIUpdatePolicyRouteRuleSetMsg` | premium/plugin-premium/policyRoute/src/main/java/org/zstack/policyRoute/APIUpdatePolicyRouteRuleSetMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/portMirror` + +### `org.zstack.header.portMirror` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangePortMirrorStateMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIChangePortMirrorStateMsg.java | +| 2 | `APICreatePortMirrorMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APICreatePortMirrorMsg.java | +| 3 | `APICreatePortMirrorSessionMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APICreatePortMirrorSessionMsg.java | +| 4 | `APIDeletePortMirrorMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIDeletePortMirrorMsg.java | +| 5 | `APIDeletePortMirrorSessionMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIDeletePortMirrorSessionMsg.java | +| 6 | `APIGetCandidateVmNicsForPortMirrorMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIGetCandidateVmNicsForPortMirrorMsg.java | +| 7 | `APIQueryPortMirrorMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIQueryPortMirrorMsg.java | +| 8 | `APIQueryPortMirrorNetworkUsedIpMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIQueryPortMirrorNetworkUsedIpMsg.java | +| 9 | `APIQueryPortMirrorSessionMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIQueryPortMirrorSessionMsg.java | +| 10 | `APIUpdatePortMirrorMsg` | premium/plugin-premium/portMirror/src/main/java/org/zstack/header/portMirror/APIUpdatePortMirrorMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/routeProtocol` + +### `org.zstack.header.protocol` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddVRouterNetworksToOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIAddVRouterNetworksToOspfAreaMsg.java | +| 2 | `APICreateVRouterOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APICreateVRouterOspfAreaMsg.java | +| 3 | `APIDeleteVRouterOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIDeleteVRouterOspfAreaMsg.java | +| 4 | `APIGetVpcAttachedOspfMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIGetVpcAttachedOspfMsg.java | +| 5 | `APIGetVRouterOspfNeighborMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIGetVRouterOspfNeighborMsg.java | +| 6 | `APIGetVRouterRouterIdMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIGetVRouterRouterIdMsg.java | +| 7 | `APIQueryVRouterOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIQueryVRouterOspfAreaMsg.java | +| 8 | `APIQueryVRouterOspfNetworkMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIQueryVRouterOspfNetworkMsg.java | +| 9 | `APIRemoveVRouterNetworksFromOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIRemoveVRouterNetworksFromOspfAreaMsg.java | +| 10 | `APISetVRouterRouterIdMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APISetVRouterRouterIdMsg.java | +| 11 | `APIUpdateVRouterOspfAreaMsg` | premium/plugin-premium/routeProtocol/src/main/java/org/zstack/header/protocol/APIUpdateVRouterOspfAreaMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/sns-aliyun-sms` + +### `org.zstack.sns.platform.aliyunsms` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSNSAliyunSmsEndpointMsg` | premium/plugin-premium/sns-aliyun-sms/src/main/java/org/zstack/sns/platform/aliyunsms/APICreateSNSAliyunSmsEndpointMsg.java | +| 2 | `APIValidateSNSAliyunSmsEndpointMsg` | premium/plugin-premium/sns-aliyun-sms/src/main/java/org/zstack/sns/platform/aliyunsms/APIValidateSNSAliyunSmsEndpointMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/software-package-plugin` + +### `org.zstack.softwarePackage.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICleanSoftwarePackageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APICleanSoftwarePackageMsg.java | +| 2 | `APIGetDirectoryUsageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIGetDirectoryUsageMsg.java | +| 3 | `APIGetUploadSoftwarePackageJobDetailsMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIGetUploadSoftwarePackageJobDetailsMsg.java | +| 4 | `APIInstallSoftwarePackageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIInstallSoftwarePackageMsg.java | +| 5 | `APIQuerySoftwarePackageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIQuerySoftwarePackageMsg.java | +| 6 | `APIUninstallSoftwarePackageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIUninstallSoftwarePackageMsg.java | +| 7 | `APIUploadSoftwarePackageMsg` | premium/plugin-premium/software-package-plugin/src/main/java/org/zstack/softwarePackage/header/APIUploadSoftwarePackageMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/sso-plugin` + +### `org.zstack.sso.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateCasClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APICreateCasClientMsg.java | +| 2 | `APICreateOAuthClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APICreateOAuthClientMsg.java | +| 3 | `APICreateSSORedirectTemplateMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APICreateSSORedirectTemplateMsg.java | +| 4 | `APIDeleteSSOClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIDeleteSSOClientMsg.java | +| 5 | `APIDeleteSSORedirectTemplateMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIDeleteSSORedirectTemplateMsg.java | +| 6 | `APIGetOAuth2TokenMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIGetOAuth2TokenMsg.java | +| 7 | `APIGetSSOClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIGetSSOClientMsg.java | +| 8 | `APIUpdateCasClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIUpdateCasClientMsg.java | +| 9 | `APIUpdateOAuthClientMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIUpdateOAuthClientMsg.java | +| 10 | `APIUpdateSSORedirectTemplateMsg` | premium/plugin-premium/sso-plugin/src/main/java/org/zstack/sso/header/APIUpdateSSORedirectTemplateMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/storage-device` + +### `org.zstack.storage.device.fibreChannel` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryFiberChannelLunMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/fibreChannel/APIQueryFiberChannelLunMsg.java | +| 2 | `APIQueryFiberChannelStorageMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/fibreChannel/APIQueryFiberChannelStorageMsg.java | +| 3 | `APIRefreshFiberChannelStorageMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/fibreChannel/APIRefreshFiberChannelStorageMsg.java | + +### `org.zstack.storage.device.hba` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIQueryFcHbaDeviceMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/hba/APIQueryFcHbaDeviceMsg.java | + +### `org.zstack.storage.device.iscsi` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddIscsiServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIAddIscsiServerMsg.java | +| 2 | `APIAttachIscsiServerToClusterMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIAttachIscsiServerToClusterMsg.java | +| 3 | `APIDeleteIscsiServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIDeleteIscsiServerMsg.java | +| 4 | `APIDetachIscsiServerFromClusterMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIDetachIscsiServerFromClusterMsg.java | +| 5 | `APIQueryIscsiLunMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIQueryIscsiLunMsg.java | +| 6 | `APIQueryIscsiServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIQueryIscsiServerMsg.java | +| 7 | `APIRefreshIscsiServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIRefreshIscsiServerMsg.java | +| 8 | `APIUpdateIscsiServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/iscsi/APIUpdateIscsiServerMsg.java | + +### `org.zstack.storage.device.localRaid` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetLocalRaidPhysicalDriveSmartMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APIGetLocalRaidPhysicalDriveSmartMsg.java | +| 2 | `APILocateLocalRaidPhysicalDriveMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APILocateLocalRaidPhysicalDriveMsg.java | +| 3 | `APIQueryLocalRaidControllerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APIQueryLocalRaidControllerMsg.java | +| 4 | `APIQueryLocalRaidPhysicalDriveMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APIQueryLocalRaidPhysicalDriveMsg.java | +| 5 | `APIQueryPhysicalDriveSelfTestHistoryMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APIQueryPhysicalDriveSelfTestHistoryMsg.java | +| 6 | `APIRefreshLocalRaidMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APIRefreshLocalRaidMsg.java | +| 7 | `APISelfTestLocalRaidMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/localRaid/APISelfTestLocalRaidMsg.java | + +### `org.zstack.storage.device.multipath` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetHostMultipathTopologyMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/multipath/APIGetHostMultipathTopologyMsg.java | + +### `org.zstack.storage.device.nvme` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddNvmeServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIAddNvmeServerMsg.java | +| 2 | `APIAttachNvmeServerToClusterMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIAttachNvmeServerToClusterMsg.java | +| 3 | `APIDeleteNvmeServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIDeleteNvmeServerMsg.java | +| 4 | `APIDetachNvmeServerFromClusterMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIDetachNvmeServerFromClusterMsg.java | +| 5 | `APIQueryNvmeLunMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIQueryNvmeLunMsg.java | +| 6 | `APIQueryNvmeServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIQueryNvmeServerMsg.java | +| 7 | `APIQueryNvmeTargetMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIQueryNvmeTargetMsg.java | +| 8 | `APIRefreshNvmeServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIRefreshNvmeServerMsg.java | +| 9 | `APIRefreshNvmeTargetMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIRefreshNvmeTargetMsg.java | +| 10 | `APIUpdateNvmeServerMsg` | premium/plugin-premium/storage-device/src/main/java/org/zstack/storage/device/nvme/APIUpdateNvmeServerMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/ticket` + +### `org.zstack.ticket.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddTicketTypesToTicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIAddTicketTypesToTicketFlowCollectionMsg.java | +| 2 | `APIChangeTicketFlowCollectionStateMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIChangeTicketFlowCollectionStateMsg.java | +| 3 | `APIChangeTicketStatusMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIChangeTicketStatusMsg.java | +| 4 | `APICreateTicketFlowMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APICreateTicketFlowMsg.java | +| 5 | `APICreateTicketMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APICreateTicketMsg.java | +| 6 | `APICreateTickFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APICreateTickFlowCollectionMsg.java | +| 7 | `APIDeleteTicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIDeleteTicketFlowCollectionMsg.java | +| 8 | `APIDeleteTicketMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIDeleteTicketMsg.java | +| 9 | `APIQueryArchiveTicketHistoryMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryArchiveTicketHistoryMsg.java | +| 10 | `APIQueryArchiveTicketMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryArchiveTicketMsg.java | +| 11 | `APIQueryTicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryTicketFlowCollectionMsg.java | +| 12 | `APIQueryTicketFlowMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryTicketFlowMsg.java | +| 13 | `APIQueryTicketHistoryMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryTicketHistoryMsg.java | +| 14 | `APIQueryTicketMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryTicketMsg.java | +| 15 | `APIQueryTicketTypeMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIQueryTicketTypeMsg.java | +| 16 | `APIRemoveTicketTypesFromTicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIRemoveTicketTypesFromTicketFlowCollectionMsg.java | +| 17 | `APIUpdateTicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIUpdateTicketFlowCollectionMsg.java | +| 18 | `APIUpdateTicketRequestMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/api/APIUpdateTicketRequestMsg.java | + +### `org.zstack.ticket.iam2.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddIAM2TicketFlowMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/iam2/api/APIAddIAM2TicketFlowMsg.java | +| 2 | `APICreateIAM2TickFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/iam2/api/APICreateIAM2TickFlowCollectionMsg.java | +| 3 | `APIDeleteIAM2TicketFlowMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/iam2/api/APIDeleteIAM2TicketFlowMsg.java | +| 4 | `APIUpdateIAM2TicketFlowCollectionMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/iam2/api/APIUpdateIAM2TicketFlowCollectionMsg.java | +| 5 | `APIUpdateIAM2TicketFlowMsg` | premium/plugin-premium/ticket/src/main/java/org/zstack/ticket/iam2/api/APIUpdateIAM2TicketFlowMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/virtualSwitchNetwork` + +### `org.zstack.network.l2.virtualSwitch.header` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBatchCreateHostKernelInterfaceMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIBatchCreateHostKernelInterfaceMsg.java | +| 2 | `APICreateHostKernelInterfaceMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APICreateHostKernelInterfaceMsg.java | +| 3 | `APICreateL2PortGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APICreateL2PortGroupMsg.java | +| 4 | `APICreateL2VirtualSwitchMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APICreateL2VirtualSwitchMsg.java | +| 5 | `APICreatePortGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APICreatePortGroupMsg.java | +| 6 | `APIDeleteHostKernelInterfaceMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIDeleteHostKernelInterfaceMsg.java | +| 7 | `APIDeletePortGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIDeletePortGroupMsg.java | +| 8 | `APIGetCandidateHostKernelInterfacesMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIGetCandidateHostKernelInterfacesMsg.java | +| 9 | `APIQueryHostKernelInterfaceMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIQueryHostKernelInterfaceMsg.java | +| 10 | `APIQueryL2PortGroupNetworkMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIQueryL2PortGroupNetworkMsg.java | +| 11 | `APIQueryL2VirtualSwitchNetworkMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIQueryL2VirtualSwitchNetworkMsg.java | +| 12 | `APIQueryPortGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIQueryPortGroupMsg.java | +| 13 | `APIQueryUplinkGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIQueryUplinkGroupMsg.java | +| 14 | `APIUpdateHostKernelInterfaceMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIUpdateHostKernelInterfaceMsg.java | +| 15 | `APIUpdatePortGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIUpdatePortGroupMsg.java | +| 16 | `APIUpdateVirtualSwitchUplinkBondingsMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIUpdateVirtualSwitchUplinkBondingsMsg.java | +| 17 | `APIUpdateVirtualSwitchUplinkGroupMsg` | premium/plugin-premium/virtualSwitchNetwork/src/main/java/org/zstack/network/l2/virtualSwitch/header/APIUpdateVirtualSwitchUplinkGroupMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/vpcFirewall` + +### `org.zstack.vpcfirewall.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIApplyRuleSetChangesMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIApplyRuleSetChangesMsg.java | +| 2 | `APIAttachFirewallRuleSetToL3Msg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIAttachFirewallRuleSetToL3Msg.java | +| 3 | `APIChangeFirewallRuleStateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIChangeFirewallRuleStateMsg.java | +| 4 | `APICheckFirewallRuleConfigFileMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICheckFirewallRuleConfigFileMsg.java | +| 5 | `APICreateFirewallIpSetTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateFirewallIpSetTemplateMsg.java | +| 6 | `APICreateFirewallRuleFromConfigFileMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateFirewallRuleFromConfigFileMsg.java | +| 7 | `APICreateFirewallRuleMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateFirewallRuleMsg.java | +| 8 | `APICreateFirewallRuleSetMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateFirewallRuleSetMsg.java | +| 9 | `APICreateFirewallRuleTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateFirewallRuleTemplateMsg.java | +| 10 | `APICreateVpcFirewallMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APICreateVpcFirewallMsg.java | +| 11 | `APIDeleteFirewallIpSetTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDeleteFirewallIpSetTemplateMsg.java | +| 12 | `APIDeleteFirewallMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDeleteFirewallMsg.java | +| 13 | `APIDeleteFirewallRuleMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDeleteFirewallRuleMsg.java | +| 14 | `APIDeleteFirewallRuleSetMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDeleteFirewallRuleSetMsg.java | +| 15 | `APIDeleteFirewallRuleTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDeleteFirewallRuleTemplateMsg.java | +| 16 | `APIDetachFirewallRuleSetFromL3Msg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIDetachFirewallRuleSetFromL3Msg.java | +| 17 | `APIQueryFirewallIpSetTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryFirewallIpSetTemplateMsg.java | +| 18 | `APIQueryFirewallRuleMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryFirewallRuleMsg.java | +| 19 | `APIQueryFirewallRuleSetL3RefMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryFirewallRuleSetL3RefMsg.java | +| 20 | `APIQueryFirewallRuleSetMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryFirewallRuleSetMsg.java | +| 21 | `APIQueryFirewallRuleTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryFirewallRuleTemplateMsg.java | +| 22 | `APIQueryVpcFirewallMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryVpcFirewallMsg.java | +| 23 | `APIQueryVpcFirewallVRouterRefMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIQueryVpcFirewallVRouterRefMsg.java | +| 24 | `APIRefreshFirewallMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIRefreshFirewallMsg.java | +| 25 | `APIUpdateFirewallIpSetTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIUpdateFirewallIpSetTemplateMsg.java | +| 26 | `APIUpdateFirewallRuleMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIUpdateFirewallRuleMsg.java | +| 27 | `APIUpdateFirewallRuleSetMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIUpdateFirewallRuleSetMsg.java | +| 28 | `APIUpdateFirewallRuleTemplateMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIUpdateFirewallRuleTemplateMsg.java | +| 29 | `APIUpdateVpcFirewallMsg` | premium/plugin-premium/vpcFirewall/src/main/java/org/zstack/vpcfirewall/api/APIUpdateVpcFirewallMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/zboxbackup` + +### `org.zstack.externalbackup.zbox` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateZBoxBackupMsg` | premium/plugin-premium/zboxbackup/src/main/java/org/zstack/externalbackup/zbox/APICreateZBoxBackupMsg.java | +| 2 | `APIGetZBoxBackupDetailsMsg` | premium/plugin-premium/zboxbackup/src/main/java/org/zstack/externalbackup/zbox/APIGetZBoxBackupDetailsMsg.java | +| 3 | `APIQueryZBoxBackupMsg` | premium/plugin-premium/zboxbackup/src/main/java/org/zstack/externalbackup/zbox/APIQueryZBoxBackupMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/zce-x-plugin` + +### `org.zstack.zcex.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddZceXMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIAddZceXMsg.java | +| 2 | `APICreateZceXAlertPlatformMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APICreateZceXAlertPlatformMsg.java | +| 3 | `APIDeleteZceXAlertPlatformMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIDeleteZceXAlertPlatformMsg.java | +| 4 | `APIGetZceXCapabilityMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIGetZceXCapabilityMsg.java | +| 5 | `APIQueryZceXMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIQueryZceXMsg.java | +| 6 | `APIQueryZceXThirdPartyPlatformAlertRefMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIQueryZceXThirdPartyPlatformAlertRefMsg.java | +| 7 | `APIRemoveZceXMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIRemoveZceXMsg.java | +| 8 | `APIUpdateZceXClusterConfigMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIUpdateZceXClusterConfigMsg.java | +| 9 | `APIZceXTestConnectionMsg` | premium/plugin-premium/zce-x-plugin/src/main/java/org/zstack/zcex/api/APIZceXTestConnectionMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/zops-plugin` + +### `org.zstack.zops.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICheckCephHealthStatusMsg` | premium/plugin-premium/zops-plugin/src/main/java/org/zstack/zops/api/APICheckCephHealthStatusMsg.java | +| 2 | `APICheckNetworkReachableMsg` | premium/plugin-premium/zops-plugin/src/main/java/org/zstack/zops/api/APICheckNetworkReachableMsg.java | +| 3 | `APIGetChronyServersMsg` | premium/plugin-premium/zops-plugin/src/main/java/org/zstack/zops/api/APIGetChronyServersMsg.java | +| 4 | `APISyncChronyServersMsg` | premium/plugin-premium/zops-plugin/src/main/java/org/zstack/zops/api/APISyncChronyServersMsg.java | +| 5 | `APIUpdateChronyServersMsg` | premium/plugin-premium/zops-plugin/src/main/java/org/zstack/zops/api/APIUpdateChronyServersMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/zstone-plugin` + +### `org.zstack.zstone.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddZStoneMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIAddZStoneMsg.java | +| 2 | `APIGetZStoneCapabilityMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIGetZStoneCapabilityMsg.java | +| 3 | `APIQueryZStoneMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIQueryZStoneMsg.java | +| 4 | `APIRemoveZStoneMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIRemoveZStoneMsg.java | +| 5 | `APIUpdateZStoneClusterConfigMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIUpdateZStoneClusterConfigMsg.java | +| 6 | `APIUpdateZStoneHostConfigMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIUpdateZStoneHostConfigMsg.java | +| 7 | `APIZStoneTestConnectionMsg` | premium/plugin-premium/zstone-plugin/src/main/java/org/zstack/zstone/api/APIZStoneTestConnectionMsg.java | + +--- + + + +## 模块:`premium/plugin-premium/zsv` + +### `org.zstack.zsv.core.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetNodeRolesMsg` | premium/plugin-premium/zsv/src/main/java/org/zstack/zsv/core/api/APIGetNodeRolesMsg.java | + +### `org.zstack.zsv.storage.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICheckCephPluginMsg` | premium/plugin-premium/zsv/src/main/java/org/zstack/zsv/storage/api/APICheckCephPluginMsg.java | + +--- + + + +## 模块:`premium/sharedblock` + +### `org.zstack.storage.primary.sharedblock` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSharedBlockGroupPrimaryStorageMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIAddSharedBlockGroupPrimaryStorageMsg.java | +| 2 | `APIAddSharedBlockToSharedBlockGroupMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIAddSharedBlockToSharedBlockGroupMsg.java | +| 3 | `APIGetSharedBlockCandidateMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIGetSharedBlockCandidateMsg.java | +| 4 | `APIQuerySharedBlockGroupPrimaryStorageHostRefMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIQuerySharedBlockGroupPrimaryStorageHostRefMsg.java | +| 5 | `APIQuerySharedBlockGroupPrimaryStorageMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIQuerySharedBlockGroupPrimaryStorageMsg.java | +| 6 | `APIQuerySharedBlockMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIQuerySharedBlockMsg.java | +| 7 | `APIRefreshSharedblockDeviceCapacityMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIRefreshSharedblockDeviceCapacityMsg.java | +| 8 | `APIUpdateSharedBlockMsg` | premium/sharedblock/src/main/java/org/zstack/storage/primary/sharedblock/APIUpdateSharedBlockMsg.java | + +--- + + + +## 模块:`premium/slb` + +### `org.zstack.network.service.slb` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSlbGroupMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APICreateSlbGroupMsg.java | +| 2 | `APICreateSlbInstanceMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APICreateSlbInstanceMsg.java | +| 3 | `APICreateSlbOfferingMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APICreateSlbOfferingMsg.java | +| 4 | `APIDeleteSlbGroupMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APIDeleteSlbGroupMsg.java | +| 5 | `APIQuerySlbGroupMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APIQuerySlbGroupMsg.java | +| 6 | `APIQuerySlbOfferingMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APIQuerySlbOfferingMsg.java | +| 7 | `APIUpdateSlbGroupMsg` | premium/slb/src/main/java/org/zstack/network/service/slb/APIUpdateSlbGroupMsg.java | + +--- + + + +## 模块:`premium/snmp` + +### `org.zstack.snmp.agent` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSnmpAgentMsg` | premium/snmp/src/main/java/org/zstack/snmp/agent/APICreateSnmpAgentMsg.java | +| 2 | `APIQuerySnmpAgentMsg` | premium/snmp/src/main/java/org/zstack/snmp/agent/APIQuerySnmpAgentMsg.java | +| 3 | `APIStartSnmpAgentMsg` | premium/snmp/src/main/java/org/zstack/snmp/agent/APIStartSnmpAgentMsg.java | +| 4 | `APIStopSnmpAgentMsg` | premium/snmp/src/main/java/org/zstack/snmp/agent/APIStopSnmpAgentMsg.java | +| 5 | `APIUpdateSnmpAgentMsg` | premium/snmp/src/main/java/org/zstack/snmp/agent/APIUpdateSnmpAgentMsg.java | + +--- + + + +## 模块:`premium/sns` + +### `org.zstack.sns` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSNSSmsReceiverMsg` | premium/sns/src/main/java/org/zstack/sns/APIAddSNSSmsReceiverMsg.java | +| 2 | `APIChangeSNSApplicationEndpointStateMsg` | premium/sns/src/main/java/org/zstack/sns/APIChangeSNSApplicationEndpointStateMsg.java | +| 3 | `APIChangeSNSApplicationPlatformStateMsg` | premium/sns/src/main/java/org/zstack/sns/APIChangeSNSApplicationPlatformStateMsg.java | +| 4 | `APIChangeSNSTopicStateMsg` | premium/sns/src/main/java/org/zstack/sns/APIChangeSNSTopicStateMsg.java | +| 5 | `APICreateSNSApplicationEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APICreateSNSApplicationEndpointMsg.java | +| 6 | `APICreateSNSApplicationPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/APICreateSNSApplicationPlatformMsg.java | +| 7 | `APICreateSNSSmsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APICreateSNSSmsEndpointMsg.java | +| 8 | `APICreateSNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APICreateSNSTopicMsg.java | +| 9 | `APIDeleteSNSApplicationEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APIDeleteSNSApplicationEndpointMsg.java | +| 10 | `APIDeleteSNSApplicationPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/APIDeleteSNSApplicationPlatformMsg.java | +| 11 | `APIDeleteSNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APIDeleteSNSTopicMsg.java | +| 12 | `APIQuerySNSApplicationEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APIQuerySNSApplicationEndpointMsg.java | +| 13 | `APIQuerySNSApplicationPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/APIQuerySNSApplicationPlatformMsg.java | +| 14 | `APIQuerySNSSmsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APIQuerySNSSmsEndpointMsg.java | +| 15 | `APIQuerySNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APIQuerySNSTopicMsg.java | +| 16 | `APIQuerySNSTopicSubscriberMsg` | premium/sns/src/main/java/org/zstack/sns/APIQuerySNSTopicSubscriberMsg.java | +| 17 | `APIRemoveSNSSmsReceiverMsg` | premium/sns/src/main/java/org/zstack/sns/APIRemoveSNSSmsReceiverMsg.java | +| 18 | `APISubscribeSNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APISubscribeSNSTopicMsg.java | +| 19 | `APIUnsubscribeSNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APIUnsubscribeSNSTopicMsg.java | +| 20 | `APIUpdateSNSApplicationEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APIUpdateSNSApplicationEndpointMsg.java | +| 21 | `APIUpdateSNSApplicationPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/APIUpdateSNSApplicationPlatformMsg.java | +| 22 | `APIUpdateSNSTopicMsg` | premium/sns/src/main/java/org/zstack/sns/APIUpdateSNSTopicMsg.java | +| 23 | `APIValidateSNSSmsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/APIValidateSNSSmsEndpointMsg.java | + +### `org.zstack.sns.platform.dingtalk` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSNSDingTalkAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIAddSNSDingTalkAtPersonMsg.java | +| 2 | `APICreateSNSDingTalkEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APICreateSNSDingTalkEndpointMsg.java | +| 3 | `APIQuerySNSDingTalkAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIQuerySNSDingTalkAtPersonMsg.java | +| 4 | `APIQuerySNSDingTalkEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIQuerySNSDingTalkEndpointMsg.java | +| 5 | `APIRemoveSNSDingTalkAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIRemoveSNSDingTalkAtPersonMsg.java | +| 6 | `APISNSDingTalkTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APISNSDingTalkTestConnectionMsg.java | +| 7 | `APIUpdateAtPersonOfAtDingTalkEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIUpdateAtPersonOfAtDingTalkEndpointMsg.java | +| 8 | `APIUpdateSNSDingTalkEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/dingtalk/APIUpdateSNSDingTalkEndpointMsg.java | + +### `org.zstack.sns.platform.email` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddEmailAddressToSNSEmailEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIAddEmailAddressToSNSEmailEndpointMsg.java | +| 2 | `APICreateSNSEmailEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APICreateSNSEmailEndpointMsg.java | +| 3 | `APICreateSNSEmailPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APICreateSNSEmailPlatformMsg.java | +| 4 | `APIDeleteEmailAddressOfSNSEmailEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIDeleteEmailAddressOfSNSEmailEndpointMsg.java | +| 5 | `APIQuerySNSEmailAddressMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIQuerySNSEmailAddressMsg.java | +| 6 | `APIQuerySNSEmailEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIQuerySNSEmailEndpointMsg.java | +| 7 | `APIQuerySNSEmailPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIQuerySNSEmailPlatformMsg.java | +| 8 | `APISNSEmailTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APISNSEmailTestConnectionMsg.java | +| 9 | `APIUpdateEmailAddressOfSNSEmailEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIUpdateEmailAddressOfSNSEmailEndpointMsg.java | +| 10 | `APIValidateSNSEmailPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/email/APIValidateSNSEmailPlatformMsg.java | + +### `org.zstack.sns.platform.feishu` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSNSFeiShuAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIAddSNSFeiShuAtPersonMsg.java | +| 2 | `APICreateSNSFeiShuEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APICreateSNSFeiShuEndpointMsg.java | +| 3 | `APIQuerySNSFeiShuAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIQuerySNSFeiShuAtPersonMsg.java | +| 4 | `APIQuerySNSFeiShuEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIQuerySNSFeiShuEndpointMsg.java | +| 5 | `APIRemoveSNSFeiShuAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIRemoveSNSFeiShuAtPersonMsg.java | +| 6 | `APISNSFeiShuTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APISNSFeiShuTestConnectionMsg.java | +| 7 | `APIUpdateAtPersonOfAtFeiShuEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIUpdateAtPersonOfAtFeiShuEndpointMsg.java | +| 8 | `APIUpdateSNSFeiShuEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/feishu/APIUpdateSNSFeiShuEndpointMsg.java | + +### `org.zstack.sns.platform.http` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSNSHttpEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/http/APICreateSNSHttpEndpointMsg.java | +| 2 | `APIQuerySNSHttpEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/http/APIQuerySNSHttpEndpointMsg.java | +| 3 | `APISNSHttpTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/http/APISNSHttpTestConnectionMsg.java | +| 4 | `APIUpdateSNSHttpEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/http/APIUpdateSNSHttpEndpointMsg.java | + +### `org.zstack.sns.platform.microsoftteams` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSNSMicrosoftTeamsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/microsoftteams/APICreateSNSMicrosoftTeamsEndpointMsg.java | +| 2 | `APIQuerySNSMicrosoftTeamsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/microsoftteams/APIQuerySNSMicrosoftTeamsEndpointMsg.java | +| 3 | `APISNSMicrosoftTeamsTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/microsoftteams/APISNSMicrosoftTeamsTestConnectionMsg.java | +| 4 | `APIUpdateSNSMicrosoftTeamsEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/microsoftteams/APIUpdateSNSMicrosoftTeamsEndpointMsg.java | + +### `org.zstack.sns.platform.snmp` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSNSSnmpEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/snmp/APICreateSNSSnmpEndpointMsg.java | +| 2 | `APICreateSNSSnmpPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/snmp/APICreateSNSSnmpPlatformMsg.java | +| 3 | `APIQuerySNSSnmpPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/snmp/APIQuerySNSSnmpPlatformMsg.java | +| 4 | `APISNSSnmpTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/snmp/APISNSSnmpTestConnectionMsg.java | +| 5 | `APIUpdateSNSSnmpPlatformMsg` | premium/sns/src/main/java/org/zstack/sns/platform/snmp/APIUpdateSNSSnmpPlatformMsg.java | + +### `org.zstack.sns.platform.wecom` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSNSWeComAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIAddSNSWeComAtPersonMsg.java | +| 2 | `APICreateSNSWeComEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APICreateSNSWeComEndpointMsg.java | +| 3 | `APIQuerySNSWeComAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIQuerySNSWeComAtPersonMsg.java | +| 4 | `APIQuerySNSWeComEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIQuerySNSWeComEndpointMsg.java | +| 5 | `APIRemoveSNSWeComAtPersonMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIRemoveSNSWeComAtPersonMsg.java | +| 6 | `APISNSWeComTestConnectionMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APISNSWeComTestConnectionMsg.java | +| 7 | `APIUpdateAtPersonOfAtWeComEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIUpdateAtPersonOfAtWeComEndpointMsg.java | +| 8 | `APIUpdateSNSWeComEndpointMsg` | premium/sns/src/main/java/org/zstack/sns/platform/wecom/APIUpdateSNSWeComEndpointMsg.java | + +--- + + + +## 模块:`premium/tag2` + +### `org.zstack.tag2` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAttachTagToResourcesMsg` | premium/tag2/src/main/java/org/zstack/tag2/APIAttachTagToResourcesMsg.java | +| 2 | `APICreateTagMsg` | premium/tag2/src/main/java/org/zstack/tag2/APICreateTagMsg.java | +| 3 | `APIDetachTagFromResourcesMsg` | premium/tag2/src/main/java/org/zstack/tag2/APIDetachTagFromResourcesMsg.java | +| 4 | `APIQueryTagMsg` | premium/tag2/src/main/java/org/zstack/tag2/APIQueryTagMsg.java | +| 5 | `APIUpdateTagMsg` | premium/tag2/src/main/java/org/zstack/tag2/APIUpdateTagMsg.java | + +--- + + + +## 模块:`premium/twoFactorAuthentication` + +### `org.zstack.twoFactorAuthentication` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIGetTwoFactorAuthenticationSecretMsg` | premium/twoFactorAuthentication/src/main/java/org/zstack/twoFactorAuthentication/APIGetTwoFactorAuthenticationSecretMsg.java | +| 2 | `APIGetTwoFactorAuthenticationStateMsg` | premium/twoFactorAuthentication/src/main/java/org/zstack/twoFactorAuthentication/APIGetTwoFactorAuthenticationStateMsg.java | +| 3 | `APIQueryTwoFactorAuthenticationMsg` | premium/twoFactorAuthentication/src/main/java/org/zstack/twoFactorAuthentication/APIQueryTwoFactorAuthenticationMsg.java | +| 4 | `APIResetTwoFactorAuthenticationSecretMsg` | premium/twoFactorAuthentication/src/main/java/org/zstack/twoFactorAuthentication/APIResetTwoFactorAuthenticationSecretMsg.java | + +--- + + + +## 模块:`premium/volumebackup` + +### `org.zstack.header.storage.database.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateDatabaseBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APICreateDatabaseBackupMsg.java | +| 2 | `APIDeleteDatabaseBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIDeleteDatabaseBackupMsg.java | +| 3 | `APIDeleteExportedDatabaseBackupFromBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIDeleteExportedDatabaseBackupFromBackupStorageMsg.java | +| 4 | `APIExportDatabaseBackupFromBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIExportDatabaseBackupFromBackupStorageMsg.java | +| 5 | `APIGetDatabaseBackupFromImageStoreMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIGetDatabaseBackupFromImageStoreMsg.java | +| 6 | `APIQueryDatabaseBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIQueryDatabaseBackupMsg.java | +| 7 | `APIRecoverDatabaseFromBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APIRecoverDatabaseFromBackupMsg.java | +| 8 | `APISyncDatabaseBackupFromImageStoreBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APISyncDatabaseBackupFromImageStoreBackupStorageMsg.java | +| 9 | `APISyncDatabaseBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/database/backup/APISyncDatabaseBackupMsg.java | + +### `org.zstack.header.storage.volume.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateDataVolumeFromVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateDataVolumeFromVolumeBackupMsg.java | +| 2 | `APICreateDataVolumeTemplateFromVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateDataVolumeTemplateFromVolumeBackupMsg.java | +| 3 | `APICreateRootVolumeTemplateFromVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateRootVolumeTemplateFromVolumeBackupMsg.java | +| 4 | `APICreateVmBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateVmBackupMsg.java | +| 5 | `APICreateVmFromVmBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateVmFromVmBackupMsg.java | +| 6 | `APICreateVmFromVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateVmFromVolumeBackupMsg.java | +| 7 | `APICreateVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APICreateVolumeBackupMsg.java | +| 8 | `APIDeleteVmBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIDeleteVmBackupMsg.java | +| 9 | `APIDeleteVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIDeleteVolumeBackupMsg.java | +| 10 | `APIQueryVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIQueryVolumeBackupMsg.java | +| 11 | `APIRecoverBackupFromImageStoreBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIRecoverBackupFromImageStoreBackupStorageMsg.java | +| 12 | `APIRecoverVmBackupFromImageStoreBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIRecoverVmBackupFromImageStoreBackupStorageMsg.java | +| 13 | `APIRevertVmFromVmBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIRevertVmFromVmBackupMsg.java | +| 14 | `APIRevertVolumeFromVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APIRevertVolumeFromVolumeBackupMsg.java | +| 15 | `APISyncBackupFromImageStoreBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APISyncBackupFromImageStoreBackupStorageMsg.java | +| 16 | `APISyncVmBackupFromImageStoreBackupStorageMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APISyncVmBackupFromImageStoreBackupStorageMsg.java | +| 17 | `APISyncVmBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APISyncVmBackupMsg.java | +| 18 | `APISyncVolumeBackupMsg` | premium/volumebackup/src/main/java/org/zstack/header/storage/volume/backup/APISyncVolumeBackupMsg.java | + +--- + + + +## 模块:`premium/vpc` + +### `org.zstack.header.vpc.ha` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeVpcHaGroupMonitorIpsMsg` | premium/vpc/src/main/java/org/zstack/header/vpc/ha/APIChangeVpcHaGroupMonitorIpsMsg.java | +| 2 | `APICreateVpcHaGroupMsg` | premium/vpc/src/main/java/org/zstack/header/vpc/ha/APICreateVpcHaGroupMsg.java | +| 3 | `APIDeleteVpcHaGroupMsg` | premium/vpc/src/main/java/org/zstack/header/vpc/ha/APIDeleteVpcHaGroupMsg.java | +| 4 | `APIQueryVpcHaGroupMsg` | premium/vpc/src/main/java/org/zstack/header/vpc/ha/APIQueryVpcHaGroupMsg.java | +| 5 | `APIUpdateVpcHaGroupMsg` | premium/vpc/src/main/java/org/zstack/header/vpc/ha/APIUpdateVpcHaGroupMsg.java | + +### `org.zstack.ipsec` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddRemoteCidrsToIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIAddRemoteCidrsToIPsecConnectionMsg.java | +| 2 | `APIAttachL3NetworksToIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIAttachL3NetworksToIPsecConnectionMsg.java | +| 3 | `APIChangeIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIChangeIPsecConnectionMsg.java | +| 4 | `APIChangeIPSecConnectionStateMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIChangeIPSecConnectionStateMsg.java | +| 5 | `APICreateIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APICreateIPsecConnectionMsg.java | +| 6 | `APIDeleteIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIDeleteIPsecConnectionMsg.java | +| 7 | `APIDetachL3NetworksFromIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIDetachL3NetworksFromIPsecConnectionMsg.java | +| 8 | `APIGetCandidateL3NetworksForIpSecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIGetCandidateL3NetworksForIpSecConnectionMsg.java | +| 9 | `APIGetVpcIPsecLogMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIGetVpcIPsecLogMsg.java | +| 10 | `APIQueryIPSecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIQueryIPSecConnectionMsg.java | +| 11 | `APIReconnectIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIReconnectIPsecConnectionMsg.java | +| 12 | `APIRemoveRemoteCidrsFromIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIRemoveRemoteCidrsFromIPsecConnectionMsg.java | +| 13 | `APIUpdateIPsecConnectionMsg` | premium/vpc/src/main/java/org/zstack/ipsec/APIUpdateIPsecConnectionMsg.java | + +### `org.zstack.vpc` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddDnsToVpcRouterMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIAddDnsToVpcRouterMsg.java | +| 2 | `APICreateVpcVRouterMsg` | premium/vpc/src/main/java/org/zstack/vpc/APICreateVpcVRouterMsg.java | +| 3 | `APIGetAttachableVpcL3NetworkMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetAttachableVpcL3NetworkMsg.java | +| 4 | `APIGetRouteTableVpcVRouterCandidateMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetRouteTableVpcVRouterCandidateMsg.java | +| 5 | `APIGetVirtualRouterSoftwareVersionMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVirtualRouterSoftwareVersionMsg.java | +| 6 | `APIGetVpcAttachedEipMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcAttachedEipMsg.java | +| 7 | `APIGetVpcAttachedIpsecMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcAttachedIpsecMsg.java | +| 8 | `APIGetVpcAttachedLoadBalancerMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcAttachedLoadBalancerMsg.java | +| 9 | `APIGetVpcAttachedPortForwardingRulesMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcAttachedPortForwardingRulesMsg.java | +| 10 | `APIGetVpcAttachedVipMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcAttachedVipMsg.java | +| 11 | `APIGetVpcVRouterDistributedRoutingConnectionsMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcVRouterDistributedRoutingConnectionsMsg.java | +| 12 | `APIGetVpcVRouterDistributedRoutingEnabledMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcVRouterDistributedRoutingEnabledMsg.java | +| 13 | `APIGetVpcVRouterNetworkServiceStateMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIGetVpcVRouterNetworkServiceStateMsg.java | +| 14 | `APIQueryVpcHaGroupNetworkServiceRefMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIQueryVpcHaGroupNetworkServiceRefMsg.java | +| 15 | `APIQueryVpcRouterMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIQueryVpcRouterMsg.java | +| 16 | `APIQueryVpcSnatStateMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIQueryVpcSnatStateMsg.java | +| 17 | `APIRemoveDnsFromVpcRouterMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIRemoveDnsFromVpcRouterMsg.java | +| 18 | `APISetVpcVRouterDistributedRoutingEnabledMsg` | premium/vpc/src/main/java/org/zstack/vpc/APISetVpcVRouterDistributedRoutingEnabledMsg.java | +| 19 | `APISetVpcVRouterNetworkServiceStateMsg` | premium/vpc/src/main/java/org/zstack/vpc/APISetVpcVRouterNetworkServiceStateMsg.java | +| 20 | `APIUpdateVirtualRouterSoftwareVersionMsg` | premium/vpc/src/main/java/org/zstack/vpc/APIUpdateVirtualRouterSoftwareVersionMsg.java | + +--- + + + +## 模块:`premium/xdragon` + +### `org.zstack.xdragon` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddXDragonHostMsg` | premium/xdragon/src/main/java/org/zstack/xdragon/APIAddXDragonHostMsg.java | + +--- + + + +## 模块:`premium/zbox` + +### `org.zstack.zbox` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddZBoxMsg` | premium/zbox/src/main/java/org/zstack/zbox/APIAddZBoxMsg.java | +| 2 | `APIEjectZBoxMsg` | premium/zbox/src/main/java/org/zstack/zbox/APIEjectZBoxMsg.java | +| 3 | `APIQueryZBoxMsg` | premium/zbox/src/main/java/org/zstack/zbox/APIQueryZBoxMsg.java | +| 4 | `APISyncZBoxCapacityMsg` | premium/zbox/src/main/java/org/zstack/zbox/APISyncZBoxCapacityMsg.java | + +--- + + + +## 模块:`premium/zwatch` + +### `org.zstack.zwatch.alarm` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAckAlarmDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAckAlarmDataMsg.java | +| 2 | `APIAckAlertDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAckAlertDataMsg.java | +| 3 | `APIAckEventDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAckEventDataMsg.java | +| 4 | `APIAddActionToAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAddActionToAlarmMsg.java | +| 5 | `APIAddActionToEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAddActionToEventSubscriptionMsg.java | +| 6 | `APIAddLabelToAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAddLabelToAlarmMsg.java | +| 7 | `APIAddLabelToEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIAddLabelToEventSubscriptionMsg.java | +| 8 | `APIChangeAlarmStateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIChangeAlarmStateMsg.java | +| 9 | `APIChangeEventSubscriptionStateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIChangeEventSubscriptionStateMsg.java | +| 10 | `APICreateAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APICreateAlarmMsg.java | +| 11 | `APIDeleteAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIDeleteAlarmMsg.java | +| 12 | `APIGetTextTemplateArgMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIGetTextTemplateArgMsg.java | +| 13 | `APIQueryAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIQueryAlarmMsg.java | +| 14 | `APIQueryAlertDataAckMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIQueryAlertDataAckMsg.java | +| 15 | `APIQueryEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIQueryEventSubscriptionMsg.java | +| 16 | `APIRemoveActionFromAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIRemoveActionFromAlarmMsg.java | +| 17 | `APIRemoveActionFromEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIRemoveActionFromEventSubscriptionMsg.java | +| 18 | `APIRemoveLabelFromAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIRemoveLabelFromAlarmMsg.java | +| 19 | `APIRemoveLabelFromEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIRemoveLabelFromEventSubscriptionMsg.java | +| 20 | `APISubscribeEventMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APISubscribeEventMsg.java | +| 21 | `APIUnsubscribeEventMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUnsubscribeEventMsg.java | +| 22 | `APIUpdateAlarmLabelMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUpdateAlarmLabelMsg.java | +| 23 | `APIUpdateAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUpdateAlarmMsg.java | +| 24 | `APIUpdateAlertDataAckMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUpdateAlertDataAckMsg.java | +| 25 | `APIUpdateEventSubscriptionLabelMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUpdateEventSubscriptionLabelMsg.java | +| 26 | `APIUpdateSubscribeEventMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/APIUpdateSubscribeEventMsg.java | + +### `org.zstack.zwatch.alarm.activealarm.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIChangeActiveAlarmStateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/activealarm/api/APIChangeActiveAlarmStateMsg.java | +| 2 | `APIGetActiveAlarmStatusMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/activealarm/api/APIGetActiveAlarmStatusMsg.java | +| 3 | `APIQueryActiveAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/activealarm/api/APIQueryActiveAlarmMsg.java | +| 4 | `APIQueryActiveAlarmTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/activealarm/api/APIQueryActiveAlarmTemplateMsg.java | +| 5 | `APIUpdateActiveAlarmTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/activealarm/api/APIUpdateActiveAlarmTemplateMsg.java | + +### `org.zstack.zwatch.alarm.sns` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/APICreateSNSTextTemplateMsg.java | +| 2 | `APIDeleteSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/APIDeleteSNSTextTemplateMsg.java | +| 3 | `APIQuerySNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/APIQuerySNSTextTemplateMsg.java | +| 4 | `APIUpdateSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/APIUpdateSNSTextTemplateMsg.java | + +### `org.zstack.zwatch.alarm.sns.template.aliyunsms` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateAliyunSmsSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/template/aliyunsms/APICreateAliyunSmsSNSTextTemplateMsg.java | +| 2 | `APIQueryAliyunSmsSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/template/aliyunsms/APIQueryAliyunSmsSNSTextTemplateMsg.java | +| 3 | `APIUpdateAliyunSmsSNSTextTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/alarm/sns/template/aliyunsms/APIUpdateAliyunSmsSNSTextTemplateMsg.java | + +### `org.zstack.zwatch.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APICreateMetricDataHttpReceiverMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APICreateMetricDataHttpReceiverMsg.java | +| 2 | `APICreateMetricTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APICreateMetricTemplateMsg.java | +| 3 | `APIDeleteMetricDataHttpReceiverMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIDeleteMetricDataHttpReceiverMsg.java | +| 4 | `APIDeleteMetricDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIDeleteMetricDataMsg.java | +| 5 | `APIDeleteMetricTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIDeleteMetricTemplateMsg.java | +| 6 | `APIGetAlarmDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetAlarmDataMsg.java | +| 7 | `APIGetAllEventMetadataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetAllEventMetadataMsg.java | +| 8 | `APIGetAllMetricMetadataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetAllMetricMetadataMsg.java | +| 9 | `APIGetAuditDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetAuditDataMsg.java | +| 10 | `APIGetEventDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetEventDataMsg.java | +| 11 | `APIGetManagementNodeDirCapacityMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetManagementNodeDirCapacityMsg.java | +| 12 | `APIGetMetricDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetMetricDataMsg.java | +| 13 | `APIGetMetricLabelValueMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetMetricLabelValueMsg.java | +| 14 | `APIGetPrometheusMetricLabelValueMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetPrometheusMetricLabelValueMsg.java | +| 15 | `APIGetZWatchAlertHistogramMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIGetZWatchAlertHistogramMsg.java | +| 16 | `APIPutMetricDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIPutMetricDataMsg.java | +| 17 | `APIQueryAlarmRecordMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIQueryAlarmRecordMsg.java | +| 18 | `APIQueryAuditMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIQueryAuditMsg.java | +| 19 | `APIQueryEventRecordMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIQueryEventRecordMsg.java | +| 20 | `APIQueryMetricDataHttpReceiverMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIQueryMetricDataHttpReceiverMsg.java | +| 21 | `APIQueryMetricTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIQueryMetricTemplateMsg.java | +| 22 | `APIUpdateAlarmDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIUpdateAlarmDataMsg.java | +| 23 | `APIUpdateEventDataMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/api/APIUpdateEventDataMsg.java | + +### `org.zstack.zwatch.monitorgroup.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddEventRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIAddEventRuleTemplateMsg.java | +| 2 | `APIAddInstanceToMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIAddInstanceToMonitorGroupMsg.java | +| 3 | `APIAddMetricRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIAddMetricRuleTemplateMsg.java | +| 4 | `APIApplyMonitorTemplateToMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIApplyMonitorTemplateToMonitorGroupMsg.java | +| 5 | `APICloneMonitorTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APICloneMonitorTemplateMsg.java | +| 6 | `APICreateMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APICreateMonitorGroupMsg.java | +| 7 | `APICreateMonitorTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APICreateMonitorTemplateMsg.java | +| 8 | `APIDeleteEventRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIDeleteEventRuleTemplateMsg.java | +| 9 | `APIDeleteMetricRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIDeleteMetricRuleTemplateMsg.java | +| 10 | `APIDeleteMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIDeleteMonitorGroupMsg.java | +| 11 | `APIDeleteMonitorTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIDeleteMonitorTemplateMsg.java | +| 12 | `APIQueryEventRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryEventRuleTemplateMsg.java | +| 13 | `APIQueryMetricRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMetricRuleTemplateMsg.java | +| 14 | `APIQueryMonitorGroupAlarmMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorGroupAlarmMsg.java | +| 15 | `APIQueryMonitorGroupEventSubscriptionMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorGroupEventSubscriptionMsg.java | +| 16 | `APIQueryMonitorGroupInstanceMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorGroupInstanceMsg.java | +| 17 | `APIQueryMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorGroupMsg.java | +| 18 | `APIQueryMonitorGroupTemplateRefMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorGroupTemplateRefMsg.java | +| 19 | `APIQueryMonitorTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIQueryMonitorTemplateMsg.java | +| 20 | `APIRemoveInstanceFromMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIRemoveInstanceFromMonitorGroupMsg.java | +| 21 | `APIRevokeMonitorTemplateFromMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIRevokeMonitorTemplateFromMonitorGroupMsg.java | +| 22 | `APIUpdateEventRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIUpdateEventRuleTemplateMsg.java | +| 23 | `APIUpdateMetricRuleTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIUpdateMetricRuleTemplateMsg.java | +| 24 | `APIUpdateMonitorGroupMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIUpdateMonitorGroupMsg.java | +| 25 | `APIUpdateMonitorTemplateMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/monitorgroup/api/APIUpdateMonitorTemplateMsg.java | + +### `org.zstack.zwatch.thirdparty.api` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddThirdpartyPlatformMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIAddThirdpartyPlatformMsg.java | +| 2 | `APIDeleteThirdpartyPlatformMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIDeleteThirdpartyPlatformMsg.java | +| 3 | `APIQuerySNSEndpointThirdpartyAlertHistoryMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIQuerySNSEndpointThirdpartyAlertHistoryMsg.java | +| 4 | `APIQueryThirdpartyAlertMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIQueryThirdpartyAlertMsg.java | +| 5 | `APIQueryThirdpartyPlatformMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIQueryThirdpartyPlatformMsg.java | +| 6 | `APIUpdateThirdpartyAlertsMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIUpdateThirdpartyAlertsMsg.java | +| 7 | `APIUpdateThirdpartyPlatformMsg` | premium/zwatch/src/main/java/org/zstack/zwatch/thirdparty/api/APIUpdateThirdpartyPlatformMsg.java | + +--- + + + +## 模块:`resourceconfig` + +### `org.zstack.resourceconfig` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIDeleteResourceConfigMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java | +| 2 | `APIGetResourceBindableConfigMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIGetResourceBindableConfigMsg.java | +| 3 | `APIGetResourceConfigMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIGetResourceConfigMsg.java | +| 4 | `APIGetResourceConfigsMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIGetResourceConfigsMsg.java | +| 5 | `APIQueryResourceConfigMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIQueryResourceConfigMsg.java | +| 6 | `APIUpdateResourceConfigMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java | +| 7 | `APIUpdateResourceConfigsMsg` | resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigsMsg.java | + +--- + + + +## 模块:`search` + +### `org.zstack.query` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIBatchQueryMsg` | search/src/main/java/org/zstack/query/APIBatchQueryMsg.java | +| 2 | `APIZQLQueryMsg` | search/src/main/java/org/zstack/query/APIZQLQueryMsg.java | + +### `org.zstack.search` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIRefreshSearchIndexesMsg` | search/src/main/java/org/zstack/search/APIRefreshSearchIndexesMsg.java | + +--- + + + +## 模块:`simulator/simulatorHeader` + +### `org.zstack.header.simulator` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSimulatorHostMsg` | simulator/simulatorHeader/src/main/java/org/zstack/header/simulator/APIAddSimulatorHostMsg.java | + +### `org.zstack.header.simulator.storage.backup` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSimulatorBackupStorageMsg` | simulator/simulatorHeader/src/main/java/org/zstack/header/simulator/storage/backup/APIAddSimulatorBackupStorageMsg.java | + +### `org.zstack.header.simulator.storage.primary` + +| # | API 消息类 | 源文件路径 | +|---|-----------|-----------| +| 1 | `APIAddSimulatorPrimaryStorageMsg` | simulator/simulatorHeader/src/main/java/org/zstack/header/simulator/storage/primary/APIAddSimulatorPrimaryStorageMsg.java | + +--- + + diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..aab3976a182 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,36 @@ +package org.zstack.header.storage.primary; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.APISyncCallMessage; +import org.zstack.header.rest.RestRequest; + + +@RestRequest( + path = "/primary-storage/vm-instances/metadata", + method = HttpMethod.GET, + responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class +) +public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements PrimaryStorageMessage { + @APIParam(resourceType = PrimaryStorageVO.class) + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getPrimaryStorageUuid() { + return uuid; + } + + public static APIGetVmInstanceMetadataFromPrimaryStorageMsg __example__() { + APIGetVmInstanceMetadataFromPrimaryStorageMsg msg = new APIGetVmInstanceMetadataFromPrimaryStorageMsg(); + msg.setUuid(uuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java new file mode 100644 index 00000000000..5e2dfa123fe --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIGetVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,26 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.APIReply; +import org.zstack.header.rest.RestResponse; + +import java.util.ArrayList; +import java.util.List; + + +@RestResponse(allTo = "all") +public class APIGetVmInstanceMetadataFromPrimaryStorageReply extends APIReply { + private List vmInstanceMetadata = new ArrayList<>(); + + public List getVmInstanceMetadata() { + return vmInstanceMetadata; + } + + public void setVmInstanceMetadata(List vmInstanceMetadata) { + this.vmInstanceMetadata = vmInstanceMetadata; + } + + public static APIGetVmInstanceMetadataFromPrimaryStorageReply __example__() { + APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply(); + return reply; + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..972cbf23154 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceEventDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.vm.VmInstanceInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "注册虚拟机返回" + + ref { + name "inventory" + path "org.zstack.header.storage.primary.APIRegisterVmInstanceEvent.inventory" + desc "null" + type "VmInstanceInventory" + since "4.10.0" + clz VmInstanceInventory.class + } + field { + name "success" + desc "" + type "boolean" + since "4.10.0" + } + ref { + name "error" + path "org.zstack.header.storage.primary.APIRegisterVmInstanceEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "4.10.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java new file mode 100644 index 00000000000..4b2c4e80778 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsg.java @@ -0,0 +1,63 @@ +package org.zstack.header.storage.primary; + +import org.springframework.http.HttpMethod; +import org.zstack.header.cluster.ClusterVO; +import org.zstack.header.host.HostVO; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +@RestRequest( + path = "/vm-instances/register", + method = HttpMethod.POST, + responseClass = APIRegisterVmInstanceReply.class, + parameterName = "params" +) +public class APIRegisterVmInstanceMsg extends APIMessage implements PrimaryStorageMessage { + @APIParam() + private String metadataPath; + @APIParam(resourceType = PrimaryStorageVO.class) + private String primaryStorageUuid; + @APIParam(resourceType = ClusterVO.class) + private String clusterUuid; + @APIParam(required = false, resourceType = HostVO.class) + private String hostUuid; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getClusterUuid() { + return clusterUuid; + } + + public void setClusterUuid(String clusterUuid) { + this.clusterUuid = clusterUuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + public static APIRegisterVmInstanceMsg __example__() { + APIRegisterVmInstanceMsg msg = new APIRegisterVmInstanceMsg(); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..9772948e4dd --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceMsgDoc_zh_cn.groovy @@ -0,0 +1,83 @@ +package org.zstack.header.storage.primary + +doc { + title "RegisterVmInstance" + + category "storage.primary" + + desc """注册虚拟机""" + + rest { + request { + url "POST /v1/vm-instances/register" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIRegisterVmInstanceMsg.class + + desc """""" + + params { + + column { + name "primaryStorageUuid" + enclosedIn "params" + desc "主存储UUID" + location "body" + type "String" + optional false + since "4.10.0" + } + column { + name "clusterUuid" + enclosedIn "params" + desc "集群UUID" + location "body" + type "String" + optional false + since "4.10.0" + } + column { + name "hostUuid" + enclosedIn "params" + desc "物理机UUID" + location "body" + type "String" + optional true + since "4.10.0" + } + column { + name "metadataPath" + enclosedIn "params" + desc "" + location "body" + type "String" + optional false + since "4.10.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "4.10.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "4.10.0" + } + } + } + + response { + clz APIRegisterVmInstanceReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceReply.java b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceReply.java new file mode 100644 index 00000000000..eb5c1fdda69 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIRegisterVmInstanceReply.java @@ -0,0 +1,103 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.allocator.HostAllocatorConstant; +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmNicInventory; +import org.zstack.header.volume.VolumeInventory; +import org.zstack.header.volume.VolumeState; +import org.zstack.header.volume.VolumeStatus; +import org.zstack.header.volume.VolumeType; +import org.zstack.utils.data.SizeUnit; + +import java.sql.Timestamp; + +import static java.util.Arrays.asList; + +@RestResponse(allTo = "inventory") +public class APIRegisterVmInstanceReply extends APIEvent { + private VmInstanceInventory inventory; + + public APIRegisterVmInstanceReply() { + } + + public APIRegisterVmInstanceReply(String apiId) { + super(apiId); + } + + public VmInstanceInventory getInventory() { + return inventory; + } + + public void setInventory(VmInstanceInventory inventory) { + this.inventory = inventory; + } + + public static APIRegisterVmInstanceReply __example__() { + APIRegisterVmInstanceReply event = new APIRegisterVmInstanceReply(); + + + String defaultL3Uuid = uuid(); + String rootVolumeUuid = uuid(); + + VmInstanceInventory vm = new VmInstanceInventory(); + vm.setName("Test-VM"); + vm.setUuid(uuid()); + vm.setAllocatorStrategy(HostAllocatorConstant.LAST_HOST_PREFERRED_ALLOCATOR_STRATEGY_TYPE); + vm.setClusterUuid(uuid()); + vm.setCpuNum(1); + vm.setCreateDate(new Timestamp(org.zstack.header.message.DocUtils.date)); + vm.setDefaultL3NetworkUuid(defaultL3Uuid); + vm.setDescription("web server VM"); + vm.setHostUuid(uuid()); + vm.setHypervisorType("KVM"); + vm.setImageUuid(uuid()); + vm.setInstanceOfferingUuid(uuid()); + vm.setLastHostUuid(uuid()); + vm.setMemorySize(SizeUnit.GIGABYTE.toByte(8)); + vm.setPlatform("Linux"); + vm.setRootVolumeUuid(rootVolumeUuid); + vm.setState(VmInstanceState.Stopped.toString()); + vm.setType(VmInstanceConstant.USER_VM_TYPE); + vm.setLastOpDate(new Timestamp(org.zstack.header.message.DocUtils.date)); + vm.setZoneUuid(uuid()); + + VolumeInventory vol = new VolumeInventory(); + vol.setName(String.format("Root-Volume-For-VM-%s", vm.getUuid())); + vol.setCreateDate(new Timestamp(org.zstack.header.message.DocUtils.date)); + vol.setLastOpDate(new Timestamp(org.zstack.header.message.DocUtils.date)); + vol.setType(VolumeType.Root.toString()); + vol.setUuid(rootVolumeUuid); + vol.setSize(SizeUnit.GIGABYTE.toByte(100)); + vol.setActualSize(SizeUnit.GIGABYTE.toByte(20)); + vol.setDeviceId(0); + vol.setState(VolumeState.Enabled.toString()); + vol.setFormat("qcow2"); + vol.setDiskOfferingUuid(uuid()); + vol.setInstallPath(String.format("/zstack_ps/rootVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-%s/%s.qcow2", rootVolumeUuid, rootVolumeUuid)); + vol.setStatus(VolumeStatus.Ready.toString()); + vol.setPrimaryStorageUuid(uuid()); + vol.setVmInstanceUuid(vm.getUuid()); + vol.setRootImageUuid(vm.getImageUuid()); + vm.setAllVolumes(asList(vol)); + + VmNicInventory nic = new VmNicInventory(); + nic.setVmInstanceUuid(vm.getUuid()); + nic.setCreateDate(vm.getCreateDate()); + nic.setLastOpDate(vm.getLastOpDate()); + nic.setDeviceId(0); + nic.setL3NetworkUuid(defaultL3Uuid); + nic.setMac("00:0c:29:bd:99:fc"); + nic.setHypervisorType("KVM"); + nic.setUuid(uuid()); + vm.setVmNics(asList(nic)); + + event.setInventory(vm); + + return event; + } + +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..d08098380d9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,17 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + + +public class GetVmInstanceMetadataFromPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java new file mode 100644 index 00000000000..cfa378a28e9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,20 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GetVmInstanceMetadataFromPrimaryStorageReply extends MessageReply { + private List vmInstanceMetadata = new ArrayList<>(); + + public List getVmInstanceMetadata() { + return vmInstanceMetadata; + } + + public void setVmInstanceMetadata(List vmInstanceMetadata) { + this.vmInstanceMetadata = vmInstanceMetadata; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..ed09f15eb4d --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java @@ -0,0 +1,21 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.VmInstanceMessage; + +public class ReadVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage { + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getVmInstanceUuid() { + return uuid; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.java new file mode 100644 index 00000000000..d5d43cb36ea --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorMsg.java @@ -0,0 +1,26 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; + +public class ReadVmInstanceMetadataOnHypervisorMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + private String metadataPath; + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + @Override + public String getHostUuid() { + return hostUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java new file mode 100644 index 00000000000..25044b944a6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataOnHypervisorReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +public class ReadVmInstanceMetadataOnHypervisorReply extends MessageReply { + private String metadata; + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java new file mode 100644 index 00000000000..04462f849ad --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +public class ReadVmInstanceMetadataReply extends MessageReply { + private String vmMetadata; + + public String getVmMetadata() { + return vmMetadata; + } + + public void setVmMetadata(String vmMetadata) { + this.vmMetadata = vmMetadata; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java b/header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java new file mode 100644 index 00000000000..cef54c1c4e5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/RegisterVmInstanceException.java @@ -0,0 +1,9 @@ +package org.zstack.header.storage.primary; + +public interface RegisterVmInstanceException { + String updateVolumeInstallPath(String installPath); + + String updateVolumeSnapshotInstallPath(String installPath); + + PrimaryStorageType getPrimaryStorageType(); +} diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.java b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.java index 57b4fab4099..cd6a33b4cd3 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotTree.java @@ -442,4 +442,24 @@ public SnapshotLeaf findSnapshot(String snapshotUuid) { return findSnapshot(arg -> arg.getUuid().equals(snapshotUuid)); } + + public List levelOrderTraversal() { + List result = new ArrayList<>(); + if (this.root == null) { + return result; + } + + Queue queue = new LinkedList<>(); + queue.offer(this.root); + + while (!queue.isEmpty()) { + SnapshotLeaf currentLeaf = queue.poll(); + result.add(currentLeaf.getInventory()); + for (SnapshotLeaf child : currentLeaf.getChildren()) { + queue.offer(child); + } + } + + return result; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.java b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.java index 2d5abaf4f7f..8aeb5873f6b 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotVO_.java @@ -1,9 +1,7 @@ package org.zstack.header.storage.snapshot; -/** - */ - import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; @StaticMetamodel(VolumeSnapshotVO.class) public class VolumeSnapshotVO_ extends VolumeSnapshotAO_ { diff --git a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEvent.java b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEvent.java old mode 100755 new mode 100644 diff --git a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceEventDoc_zh_cn.groovy old mode 100755 new mode 100644 diff --git a/header/src/main/java/org/zstack/header/vm/MetadataImpact.java b/header/src/main/java/org/zstack/header/vm/MetadataImpact.java new file mode 100644 index 00000000000..8c982b7687f --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/MetadataImpact.java @@ -0,0 +1,71 @@ +package org.zstack.header.vm; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注 API 消息对虚拟机元数据的影响类型。 + * + *

opt-out 策略

+ *

不标注时默认行为等同于 {@link Impact#CONFIG}。 + * 明确不影响元数据的 API 应标注 {@link Impact#NONE}。

+ * + *

vmUuid 解析

+ *

不涉及 VM 的 API(如 APICreateZoneMsg)即使默认 CONFIG, + * 也不会触发元数据更新——因为 {@link VmUuidFromApiResolver} 无法解析出 vmUuid, + * 不会产生 {@link UpdateVmInstanceMetadataMsg}。

+ * + * @see VmUuidFromApiResolver + * @see UpdateVmInstanceMetadataMsg + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetadataImpact { + + /** + * 影响类型。 + */ + Impact value(); + + /** + * API 失败时是否也需要更新元数据。 + * + *

默认 false:仅在 API 成功后触发元数据更新。 + * 设为 true 时,API 执行失败也会触发 markDirty。 + * 适用于 API 可能部分成功、需要同步最新状态的场景。

+ */ + boolean updateOnFailure() default false; + + /** + * API 对虚拟机元数据的影响类型枚举。 + */ + enum Impact { + /** + * 不影响虚拟机元数据,明确跳过。 + * + *

用于标注与 VM 无关或虽关联 VM 但不影响元数据内容的 API, + * 如 APIQueryVmInstanceMsg、APIGetVmConsoleAddressMsg 等。

+ */ + NONE, + + /** + * 影响虚拟机配置,触发元数据更新。 + * + *

如修改 CPU/内存、增删 SystemTag/ResourceConfig 等。 + * 这是未标注 {@link MetadataImpact} 注解时的默认行为。

+ */ + CONFIG, + + /** + * 影响存储结构,触发元数据更新。 + * + *

如存储迁移、快照操作、删除云盘等涉及存储结构变更的 API。 + * 在 sblk 场景下会设置 pending_op=2 以标记存储结构变更。

+ */ + STORAGE + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..e918ad6b075 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataMsg.java @@ -0,0 +1,43 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.NeedReplyMessage; + +/** + * 更新虚拟机元数据消息(MN 内部)。 + * + *

调用链第 1 步:由 API 完成后的拦截器发出,路由到 VM 所在的 MN 节点。 + * 接收方从 DB 构建 {@link VmInstanceMetadataDTO},编码后发送 + * {@link UpdateVmInstanceMetadataOnPrimaryStorageMsg}。

+ * + * @see UpdateVmInstanceMetadataOnPrimaryStorageMsg + * @see UpdateVmInstanceMetadataOnHypervisorMsg + */ +public class UpdateVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage { + + private String vmInstanceUuid; + + /** + * 是否涉及存储结构变更。 + * + *

对应 {@link MetadataImpact.Impact#STORAGE} 类型的操作。 + * sblk 场景下会设置 pending_op=2。

+ */ + private boolean storageStructureChange; + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public boolean isStorageStructureChange() { + return storageStructureChange; + } + + public void setStorageStructureChange(boolean storageStructureChange) { + this.storageStructureChange = storageStructureChange; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.java new file mode 100644 index 00000000000..61e0ebde900 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorMsg.java @@ -0,0 +1,86 @@ +package org.zstack.header.vm; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; + +/** + * 在 Hypervisor 上更新虚拟机元数据消息。 + * + *

调用链第 3 步(可选):发送到 Host Agent 执行实际的存储写入。

+ * + *

使用场景

+ *
    + *
  • sblk:需要通过 Host Agent 操作 LV(activate → write → deactivate)
  • + *
  • local:数据在本地磁盘,需要通过 Host Agent 写入
  • + *
  • NFS:通常通过 PS Agent 直接操作,不使用此消息
  • + *
+ * + * @see UpdateVmInstanceMetadataMsg + * @see UpdateVmInstanceMetadataOnPrimaryStorageMsg + */ +public class UpdateVmInstanceMetadataOnHypervisorMsg extends NeedReplyMessage implements HostMessage { + + private String hostUuid; + private String vmInstanceUuid; + + /** + * 元数据文件在存储上的路径。 + * + *
    + *
  • sblk:LV 设备路径,如 /dev/{vg_uuid}/{vm_uuid}_vmmeta
  • + *
  • local:本地文件路径,如 /path/to/vm/vm_metadata.json
  • + *
+ */ + private String metadataPath; + + /** + * 元数据 JSON 字符串。 + */ + private String metadata; + + /** + * 是否涉及存储结构变更(sblk 场景设置 pending_op=2)。 + */ + private boolean storageStructureChange; + + @Override + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public boolean isStorageStructureChange() { + return storageStructureChange; + } + + public void setStorageStructureChange(boolean storageStructureChange) { + this.storageStructureChange = storageStructureChange; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.java new file mode 100644 index 00000000000..036403b01b2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnHypervisorReply.java @@ -0,0 +1,9 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.MessageReply; + +/** + * {@link UpdateVmInstanceMetadataOnHypervisorMsg} 的回复。 + */ +public class UpdateVmInstanceMetadataOnHypervisorReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java new file mode 100644 index 00000000000..41ba6d9b254 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java @@ -0,0 +1,84 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.storage.primary.PrimaryStorageMessage; + +/** + * 在主存储上更新虚拟机元数据消息。 + * + *

调用链第 2 步:发送到主存储服务,由主存储根据自身类型决定写入方式: + *

    + *
  • sblk/local:进一步发送 {@link UpdateVmInstanceMetadataOnHypervisorMsg} 到 Host Agent
  • + *
  • NFS:直接通过 PS Agent 写入
  • + *
+ * + * @see UpdateVmInstanceMetadataMsg + * @see UpdateVmInstanceMetadataOnHypervisorMsg + */ +public class UpdateVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + + private String primaryStorageUuid; + private String vmInstanceUuid; + + /** + * 根盘 UUID,用于 PS handler 定位元数据写入路径。 + * + *

LocalStorage 通过根盘 installPath 推导元数据文件路径; + * NFS 通过根盘关联的 Host 确定转发目标。

+ */ + private String rootVolumeUuid; + + /** + * 元数据 JSON 字符串。 + * + *

由 {@code VmInstanceBase.buildVmInstanceMetadata()} 从 DB 全量构建, + * 为 {@link VmInstanceMetadataDTO} 的 JSON 序列化结果。

+ */ + private String metadata; + + /** + * 是否涉及存储结构变更(sblk 场景设置 pending_op=2)。 + */ + private boolean storageStructureChange; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getRootVolumeUuid() { + return rootVolumeUuid; + } + + public void setRootVolumeUuid(String rootVolumeUuid) { + this.rootVolumeUuid = rootVolumeUuid; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public boolean isStorageStructureChange() { + return storageStructureChange; + } + + public void setStorageStructureChange(boolean storageStructureChange) { + this.storageStructureChange = storageStructureChange; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.java new file mode 100644 index 00000000000..475855f2b67 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataOnPrimaryStorageReply.java @@ -0,0 +1,9 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.MessageReply; + +/** + * {@link UpdateVmInstanceMetadataOnPrimaryStorageMsg} 的回复。 + */ +public class UpdateVmInstanceMetadataOnPrimaryStorageReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.java b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.java new file mode 100644 index 00000000000..61a2d4dbd1b --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/UpdateVmInstanceMetadataReply.java @@ -0,0 +1,9 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.MessageReply; + +/** + * {@link UpdateVmInstanceMetadataMsg} 的回复。 + */ +public class UpdateVmInstanceMetadataReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java index 9d0efdd77f1..a2716386957 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java @@ -96,4 +96,6 @@ enum Capability { String VM_CDROM_OCCUPANT_ISO = "ISO"; String VM_CDROM_OCCUPANT_GUEST_TOOLS = "GuestTools"; + + String VM_META_SUFFIX = "_meta"; } diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataCodec.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataCodec.java new file mode 100644 index 00000000000..2153f9247cb --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataCodec.java @@ -0,0 +1,96 @@ +package org.zstack.header.vm; + +import org.zstack.utils.gson.JSONObjectUtil; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * 虚拟机元数据编解码器。 + * + *

负责 {@link VmInstanceMetadataDTO} 与存储介质之间的编解码: + *

+ *   序列化流程:DTO → JSON String → Base64 String → byte[](写入存储)
+ *   反序列化流程:byte[](读取存储) → Base64 String → JSON String → DTO
+ * 
+ * + *

单层 Base64 编码策略:DTO 内部所有字段为明文 JSON, + * 仅在写入存储时做一次 Base64 编码。

+ */ +public class VmInstanceMetadataCodec { + + private VmInstanceMetadataCodec() { + } + + /** + * 将 DTO 编码为可写入存储的字节数组。 + * + * @param dto 元数据 DTO + * @return Base64 编码后的字节数组 + */ + public static byte[] encode(VmInstanceMetadataDTO dto) { + String json = JSONObjectUtil.toJsonString(dto); + return Base64.getEncoder().encode(json.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 将 DTO 编码为 Base64 字符串。 + * + * @param dto 元数据 DTO + * @return Base64 编码后的字符串 + */ + public static String encodeToString(VmInstanceMetadataDTO dto) { + String json = JSONObjectUtil.toJsonString(dto); + return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 从存储读取的字节数组解码为 DTO。 + * + * @param data Base64 编码的字节数组 + * @return 元数据 DTO + * @throws IllegalArgumentException 如果 Base64 解码失败或 JSON 格式错误 + */ + public static VmInstanceMetadataDTO decode(byte[] data) { + byte[] jsonBytes = Base64.getDecoder().decode(data); + String json = new String(jsonBytes, StandardCharsets.UTF_8); + return JSONObjectUtil.toObject(json, VmInstanceMetadataDTO.class); + } + + /** + * 从 Base64 字符串解码为 DTO。 + * + * @param base64 Base64 编码的字符串 + * @return 元数据 DTO + * @throws IllegalArgumentException 如果 Base64 解码失败或 JSON 格式错误 + */ + public static VmInstanceMetadataDTO decodeFromString(String base64) { + byte[] jsonBytes = Base64.getDecoder().decode(base64); + String json = new String(jsonBytes, StandardCharsets.UTF_8); + return JSONObjectUtil.toObject(json, VmInstanceMetadataDTO.class); + } + + /** + * 将 DTO 序列化为 JSON 字符串(不做 Base64 编码)。 + * + *

用于调试、日志、一致性检查等场景。

+ * + * @param dto 元数据 DTO + * @return JSON 字符串 + */ + public static String toJson(VmInstanceMetadataDTO dto) { + return JSONObjectUtil.toJsonString(dto); + } + + /** + * 从 JSON 字符串反序列化为 DTO(不做 Base64 解码)。 + * + *

用于调试、测试等场景。

+ * + * @param json JSON 字符串 + * @return 元数据 DTO + */ + public static VmInstanceMetadataDTO fromJson(String json) { + return JSONObjectUtil.toObject(json, VmInstanceMetadataDTO.class); + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataConstants.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataConstants.java new file mode 100644 index 00000000000..ec9b5a231ee --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataConstants.java @@ -0,0 +1,82 @@ +package org.zstack.header.vm; + +/** + * 虚拟机元数据相关常量。 + */ +public class VmInstanceMetadataConstants { + + private VmInstanceMetadataConstants() { + } + + /** + * 元数据 LV 后缀(sblk 场景)。 + * + *

LV 命名规则:{vm_uuid}_vmmeta

+ */ + public static final String SBLK_LV_SUFFIX = "_vmmeta"; + + /** + * 元数据文件名(local/NFS 场景)。 + * + *

文件位于与根盘同目录下。

+ */ + public static final String METADATA_FILE_NAME = "vm_metadata.json"; + + /** + * sblk 元数据 LV 默认初始大小(字节):4MB。 + */ + public static final long SBLK_LV_INITIAL_SIZE = 4L * 1024 * 1024; + + /** + * sblk 元数据 LV 最大大小(字节):64MB。 + */ + public static final long SBLK_LV_MAX_SIZE = 64L * 1024 * 1024; + + /** + * sblk 写入序列号最大值。溢出后回绕到 1。 + */ + public static final long MAX_WRITE_SEQUENCE = 0xFFFFFFFFFFFFFFFFL; + + /** + * 全局配置:是否启用虚拟机元数据记录。 + * + *

默认关闭。开启后,API 操作成功时自动触发元数据更新。

+ */ + public static final String GLOBAL_CONFIG_METADATA_ENABLED = "vm.metadata.enabled"; + + /** + * GC 初始延迟秒数。 + * + *

API 成功后延迟该秒数再触发元数据更新, + * 避免短时间内多次 API 操作产生过多无用更新。

+ */ + public static final int INITIAL_GC_DELAY_SECONDS = 5; + + /** + * 注册虚拟机 MN 标识 System Tag 前缀。 + * + *

注册过程中在 VM 上打标记,记录执行注册的 MN UUID, + * 用于 MN 崩溃后的事务回滚判断。

+ */ + public static final String REGISTERING_MN_TAG_PREFIX = "vmMetadata::registeringMnUuid::"; + + /** + * VM 状态:注册中。 + * + *

注册开始时 VM 进入此中间状态,注册完成后转为 Stopped。

+ */ + public static final String VM_STATE_REGISTERING = "Registering"; + + /** + * ChainTask 最大排队任务数。 + * + *

同一 VM 的元数据更新 ChainTask 最多排队 1 个, + * 超出的通过 exceedMaxPendingCallback 立即 Done。

+ */ + public static final int MAX_PENDING_METADATA_TASKS = 1; + + /** + * ChainTask syncSignature 前缀。 + */ + public static final String CHAIN_TASK_SIGNATURE_PREFIX = "vm-metadata-update-"; +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataDTO.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataDTO.java new file mode 100644 index 00000000000..89afda3759b --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataDTO.java @@ -0,0 +1,132 @@ +package org.zstack.header.vm; + +import java.util.List; +import java.util.Map; + +/** + * 虚拟机元数据 DTO。 + * + *

存储在主存储上的元数据文件内容就是该 DTO 的 JSON 字符串经 Base64 编码后的结果。

+ * + *

编码策略

+ *

DTO 内部所有字段均为明文 JSON。由存储写入层对整个 DTO 的 JSON 字符串做一次统一 + * Base64 编码后写入存储介质(sblk Slot Payload / local NFS 文件内容)。

+ * + *

Checksum

+ *

Checksum 不作为 DTO 字段,由存储层保证: + *

    + *
  • sblk: Slot 结构自带 Checksum 字段
  • + *
  • local/NFS: tmp + rename 原子写入保证完整性
  • + *
+ */ +public class VmInstanceMetadataDTO { + + /** + * 资源元数据子结构。 + * + *

对于每种资源(VM、Volume、Nic),记录其 VO 全量 JSON 及关联的 SystemTag/ResourceConfig。

+ */ + public static class ResourceMetadata { + /** + * 资源 UUID。 + * + *

冗余字段,反序列化时必须校验与 {@link #vo} 内部的 uuid 字段一致。

+ */ + public String resourceUuid; + + /** + * VO 全量 JSON 明文。 + * + *
    + *
  • {@link VmInstanceMetadataDTO#vm} → VmInstanceVO JSON
  • + *
  • {@link VmInstanceMetadataDTO#volumes} 元素 → VolumeVO JSON
  • + *
  • {@link VmInstanceMetadataDTO#nics} 元素 → VmNicVO JSON
  • + *
+ * + *

序列化时由 Gson 自动处理嵌套 JSON 的转义;反序列化时需要二次反序列化为具体 VO 类。

+ */ + public String vo; + + /** + * SystemTag 列表的 Base64 编码。 + * + *

构建过程:SystemTagVO 列表 → 逐个 JSON 序列化 → 组成 JSON Array 字符串 → Base64 编码。 + * Base64 编码是为了保护可能包含的密码、密钥等敏感信息。

+ */ + public String systemTags; + + /** + * ResourceConfig 列表的 Base64 编码。 + * + *

构建过程与 systemTags 一致。

+ */ + public String resourceConfigs; + } + + /** + * 元数据 schema 版本,与 ZStack 数据库版本(zsv)一致,如 "5.0.0"。 + * + *

序列化时自动填充当前平台版本。注册时若版本不匹配则拒绝注册。 + * 升级后通过全量更新 GC 将所有 VM 的元数据刷新到新版本。

+ */ + public String schemaVersion; + + /** + * 虚拟机自身的元数据。 + * + *

{@link ResourceMetadata#vo} 为 VmInstanceVO 的 JSON。

+ */ + public ResourceMetadata vm; + + /** + * 云盘元数据列表。 + * + *

包含根盘与数据盘(挂载的 + 已卸载但 lastVmInstanceUuid 指向本 VM 的)。 + * {@link ResourceMetadata#vo} 为 VolumeVO 的 JSON。

+ */ + public List volumes; + + /** + * 网卡元数据列表。 + * + *

仅记录,注册时不恢复。{@link ResourceMetadata#vo} 为 VmNicVO 的 JSON。

+ */ + public List nics; + + /** + * 快照数据。 + * + *

Key 为 volumeUuid,Value 为该 volume 下所有 VolumeSnapshotVO 的 JSON 明文列表。

+ */ + public Map> snapshots; + + /** + * 快照组列表。 + * + *

每个元素是 VolumeSnapshotGroupVO 的 JSON 明文。

+ */ + public List snapshotGroups; + + /** + * 快照组关联引用列表。 + * + *

每个元素是 VolumeSnapshotGroupRefVO 的 JSON 明文。 + * 通过 {@code volumeSnapshotGroupUuid} 字段与 {@link #snapshotGroups} 关联。

+ */ + public List snapshotGroupRefs; + + /** + * 快照引用数据。 + * + *

Key 为 volumeUuid,Value 为该 volume 下所有 VolumeSnapshotReferenceVO 的 JSON 明文列表。 + * 使用 {@code List} 而非单值,因为同一 volume 可能存在多条引用记录。

+ */ + public Map> snapshotReferences; + + /** + * 快照引用树数据。 + * + *

Key 为 volumeUuid,Value 为该 volume 下所有 VolumeSnapshotReferenceTreeVO 的 JSON 明文列表。

+ */ + public Map> snapshotReferenceTrees; +} diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataRegistrationSpec.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataRegistrationSpec.java new file mode 100644 index 00000000000..a3ea3981759 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataRegistrationSpec.java @@ -0,0 +1,92 @@ +package org.zstack.header.vm; + +/** + * 虚拟机元数据注册参数。 + * + *

封装从元数据注册虚拟机时需要的新环境上下文信息。

+ * + *

字段处理矩阵中标记为"API 参数"或"替换"的字段,其新值来源于此对象。

+ */ +public class VmInstanceMetadataRegistrationSpec { + + /** + * 注册目标 Zone UUID(必填)。 + * + *

替换 VmInstanceVO.zoneUuid。

+ */ + private String zoneUuid; + + /** + * 注册目标主存储 UUID(必填)。 + * + *

替换 VolumeVO.primaryStorageUuid、VolumeSnapshotVO.primaryStorageUuid。

+ */ + private String primaryStorageUuid; + + /** + * 注册操作的账户 UUID。 + * + *

替换所有 VO 的 accountUuid 字段。通常为 admin。

+ */ + private String accountUuid; + + /** + * 旧存储路径标识符。 + * + *
    + *
  • sblk 场景:旧 VG UUID
  • + *
  • local/NFS 场景:旧路径前缀(如 /vms_ds)
  • + *
+ */ + private String oldPathIdentifier; + + /** + * 新存储路径标识符。 + * + *
    + *
  • sblk 场景:新 VG UUID
  • + *
  • local/NFS 场景:新路径前缀(如 /vms_ds2)
  • + *
+ */ + private String newPathIdentifier; + + public String getZoneUuid() { + return zoneUuid; + } + + public void setZoneUuid(String zoneUuid) { + this.zoneUuid = zoneUuid; + } + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getAccountUuid() { + return accountUuid; + } + + public void setAccountUuid(String accountUuid) { + this.accountUuid = accountUuid; + } + + public String getOldPathIdentifier() { + return oldPathIdentifier; + } + + public void setOldPathIdentifier(String oldPathIdentifier) { + this.oldPathIdentifier = oldPathIdentifier; + } + + public String getNewPathIdentifier() { + return newPathIdentifier; + } + + public void setNewPathIdentifier(String newPathIdentifier) { + this.newPathIdentifier = newPathIdentifier; + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataValidator.java b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataValidator.java new file mode 100644 index 00000000000..2a9722511ad --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceMetadataValidator.java @@ -0,0 +1,143 @@ +package org.zstack.header.vm; + +import org.zstack.header.exception.CloudRuntimeException; +import org.zstack.utils.gson.JSONObjectUtil; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 虚拟机元数据校验器。 + * + *

在反序列化后、注册前执行校验,确保元数据完整性和一致性。

+ * + *

校验项: + *

    + *
  • schemaVersion 与当前平台版本匹配
  • + *
  • ResourceMetadata.resourceUuid 与 vo 内部 uuid 一致
  • + *
  • snapshotGroupRefs 引用的 groupUuid 必须存在于 snapshotGroups 中
  • + *
+ */ +public class VmInstanceMetadataValidator { + + private VmInstanceMetadataValidator() { + } + + /** + * 执行全量校验。 + * + * @param dto 待校验的元数据 DTO + * @param currentVersion 当前平台 schema 版本 + * @throws CloudRuntimeException 校验失败时抛出 + */ + public static void validate(VmInstanceMetadataDTO dto, String currentVersion) { + validateSchemaVersion(dto, currentVersion); + validateResourceUuidConsistency(dto); + validateSnapshotGroupIntegrity(dto); + } + + /** + * 校验 schema 版本是否匹配当前平台版本。 + * + * @param dto 待校验的元数据 DTO + * @param currentVersion 当前平台 schema 版本 + * @throws CloudRuntimeException 版本缺失或不匹配时抛出 + */ + public static void validateSchemaVersion(VmInstanceMetadataDTO dto, String currentVersion) { + if (dto.schemaVersion == null || dto.schemaVersion.isEmpty()) { + throw new CloudRuntimeException("metadata schemaVersion is missing"); + } + if (!dto.schemaVersion.equals(currentVersion)) { + throw new CloudRuntimeException(String.format( + "metadata schemaVersion[%s] does not match current platform version[%s]," + + " please upgrade metadata first", + dto.schemaVersion, currentVersion)); + } + } + + /** + * 校验所有 ResourceMetadata 的 resourceUuid 与 vo 内部 uuid 一致。 + * + * @param dto 待校验的元数据 DTO + * @throws CloudRuntimeException resourceUuid 缺失或与 vo.uuid 不一致时抛出 + */ + public static void validateResourceUuidConsistency(VmInstanceMetadataDTO dto) { + if (dto.vm != null) { + validateSingleResourceUuid(dto.vm, "vm"); + } + if (dto.volumes != null) { + for (int i = 0; i < dto.volumes.size(); i++) { + validateSingleResourceUuid(dto.volumes.get(i), "volumes[" + i + "]"); + } + } + if (dto.nics != null) { + for (int i = 0; i < dto.nics.size(); i++) { + validateSingleResourceUuid(dto.nics.get(i), "nics[" + i + "]"); + } + } + } + + @SuppressWarnings("unchecked") + private static void validateSingleResourceUuid(VmInstanceMetadataDTO.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 voMap = JSONObjectUtil.toObject(rm.vo, Map.class); + Object voUuid = voMap.get("uuid"); + if (voUuid == null) { + throw new CloudRuntimeException(String.format( + "metadata %s.vo does not contain uuid field", path)); + } + if (!rm.resourceUuid.equals(voUuid.toString())) { + throw new CloudRuntimeException(String.format( + "metadata %s.resourceUuid[%s] does not match vo.uuid[%s]", + path, rm.resourceUuid, voUuid)); + } + } + + /** + * 校验快照组引用的完整性。 + * + *

snapshotGroupRefs 中引用的 volumeSnapshotGroupUuid + * 必须存在于 snapshotGroups 中。

+ * + * @param dto 待校验的元数据 DTO + * @throws CloudRuntimeException 引用了不存在的 group 时抛出 + */ + @SuppressWarnings("unchecked") + public static void validateSnapshotGroupIntegrity(VmInstanceMetadataDTO dto) { + if (dto.snapshotGroupRefs == null || dto.snapshotGroupRefs.isEmpty()) { + return; + } + if (dto.snapshotGroups == null || dto.snapshotGroups.isEmpty()) { + throw new CloudRuntimeException( + "metadata has snapshotGroupRefs but no snapshotGroups"); + } + + Set groupUuids = new HashSet<>(); + for (String groupJson : dto.snapshotGroups) { + Map groupMap = JSONObjectUtil.toObject(groupJson, Map.class); + Object uuid = groupMap.get("uuid"); + if (uuid != null) { + groupUuids.add(uuid.toString()); + } + } + + for (String refJson : dto.snapshotGroupRefs) { + Map refMap = JSONObjectUtil.toObject(refJson, Map.class); + Object groupUuid = refMap.get("volumeSnapshotGroupUuid"); + if (groupUuid != null && !groupUuids.contains(groupUuid.toString())) { + throw new CloudRuntimeException(String.format( + "metadata snapshotGroupRef references non-existent group[uuid:%s]", + groupUuid)); + } + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/VmMetadata.java b/header/src/main/java/org/zstack/header/vm/VmMetadata.java new file mode 100644 index 00000000000..92452753804 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmMetadata.java @@ -0,0 +1,44 @@ +package org.zstack.header.vm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VmMetadata { + public String vmInstanceVO; + public List vmSystemTags = new ArrayList<>(); + public List vmResourceConfigs = new ArrayList<>(); + + public List volumeVOs = new ArrayList<>(); + // key = volumeUuid + // value = SystemTag + public Map> volumeSystemTags = new HashMap<>(); + // key = volumeUuid + // value = ResourceConfig + public Map> volumeResourceConfigs = new HashMap<>(); + + public List vmNicVOs = new ArrayList<>(); + // key = nicUuid + // value = SystemTag + public Map> vmNicSystemTags = new HashMap<>(); + // key = nicUuid + // value = ResourceConfig + public Map> vmNicResourceConfigs = new HashMap<>(); + + // key = volumeUuid + // value = List + public Map> volumeSnapshots = new HashMap<>(); + + // VolumeSnapshotGroupVO.toString + public List volumeSnapshotGroupVO = new ArrayList<>(); + // VolumeSnapshotGroupRefVO.toString + public List volumeSnapshotGroupRefVO = new ArrayList<>(); + + // key = volumeUuid + // value = VolumeSnapshotReferenceVO.toString + public Map volumeSnapshotReferenceVO = new HashMap<>(); + // key = volumeUuid + // value = VolumeSnapshotReferenceTreeVO.toString + public Map volumeSnapshotReferenceTreeVO = new HashMap<>(); +} diff --git a/header/src/main/java/org/zstack/header/vm/VmMetadataCanonicalEvents.java b/header/src/main/java/org/zstack/header/vm/VmMetadataCanonicalEvents.java new file mode 100644 index 00000000000..dd5323fea7e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmMetadataCanonicalEvents.java @@ -0,0 +1,31 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.NeedJsonSchema; + +/** + * 虚拟机元数据相关 CanonicalEvent 定义。 + * + *

通过 {@code EventFacade.fire()} 发布,供监控系统和巡检机制消费。

+ */ +public class VmMetadataCanonicalEvents { + + /** + * GC 放弃后的 stale 事件路径。 + * + *

当 {@code UpdateVmInstanceMetadataGC} 超过最大重试次数后发布此事件, + * {@code MetadataHealthCheckJob} 监听此事件将 VM 加入优先刷新队列。

+ */ + public static final String VM_METADATA_STALE_PATH = "/vm/metadata/stale"; + + @NeedJsonSchema + public static class MetadataStaleData { + public String vmInstanceUuid; + + public MetadataStaleData() { + } + + public MetadataStaleData(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + } +} diff --git a/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO.java b/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO.java new file mode 100644 index 00000000000..32e023d5031 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO.java @@ -0,0 +1,132 @@ +package org.zstack.header.vm; + +import org.zstack.header.managementnode.ManagementNodeVO; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ForeignKey.ReferenceOption; + +import javax.persistence.*; +import java.sql.Timestamp; + +/** + * 记录 VM 元数据的"脏标记",表示该 VM 的元数据需要写入主存储。 + * + *

设计要点

+ *
    + *
  • vmInstanceUuid 做主键:一个 VM 最多一行,天然去重。 + * 100 个 API 只产生 1 行,不是 100 行。
  • + *
  • managementNodeUuid FK SET_NULL:MN 宕机后 DB 约束自动释放认领, + * 无需额外孤儿扫描。
  • + *
  • vmInstanceUuid FK CASCADE:VM 销毁时自动删除脏标记,无残留。
  • + *
  • dirtyVersion:每次 markDirty +1,刷写前快照 version, + * 成功后比较——检测刷写期间是否有新变更。语义比时间戳比较更明确,无精度问题。
  • + *
  • nextRetryTime:退避控制,失败后不立刻重试,等到下次重试时间。
  • + *
+ * + *

行语义

+ *
    + *
  • 行存在 = VM 元数据是脏的(需要刷写)
  • + *
  • 行不存在 = VM 元数据已是最新(或 VM 不存在)
  • + *
  • managementNodeUuid != null = 该行已被某个 MN 认领,正在处理
  • + *
  • managementNodeUuid == null = 该行未被认领,可被 Poller 或 triggerFlush 认领
  • + *
+ */ +@Entity +@Table(name = "VmMetadataDirtyVO") +public class VmMetadataDirtyVO { + + @Id + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE) + private String vmInstanceUuid; + + @Column + @ForeignKey(parentEntityClass = ManagementNodeVO.class, onDeleteAction = ReferenceOption.SET_NULL) + private String managementNodeUuid; + + @Column + private long dirtyVersion; + + @Column + private boolean storageStructureChange; + + @Column + private int retryCount; + + @Column + private Timestamp nextRetryTime; + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; + + @PreUpdate + private void preUpdate() { + lastOpDate = null; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getManagementNodeUuid() { + return managementNodeUuid; + } + + public void setManagementNodeUuid(String managementNodeUuid) { + this.managementNodeUuid = managementNodeUuid; + } + + public long getDirtyVersion() { + return dirtyVersion; + } + + public void setDirtyVersion(long dirtyVersion) { + this.dirtyVersion = dirtyVersion; + } + + public boolean isStorageStructureChange() { + return storageStructureChange; + } + + public void setStorageStructureChange(boolean storageStructureChange) { + this.storageStructureChange = storageStructureChange; + } + + public int getRetryCount() { + return retryCount; + } + + public void setRetryCount(int retryCount) { + this.retryCount = retryCount; + } + + public Timestamp getNextRetryTime() { + return nextRetryTime; + } + + public void setNextRetryTime(Timestamp nextRetryTime) { + this.nextRetryTime = nextRetryTime; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO_.java b/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO_.java new file mode 100644 index 00000000000..8ed099d9d8e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmMetadataDirtyVO_.java @@ -0,0 +1,17 @@ +package org.zstack.header.vm; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmMetadataDirtyVO.class) +public class VmMetadataDirtyVO_ { + public static volatile SingularAttribute vmInstanceUuid; + public static volatile SingularAttribute managementNodeUuid; + public static volatile SingularAttribute dirtyVersion; + public static volatile SingularAttribute storageStructureChange; + public static volatile SingularAttribute retryCount; + public static volatile SingularAttribute nextRetryTime; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/vm/VmUuidFromApiResolver.java b/header/src/main/java/org/zstack/header/vm/VmUuidFromApiResolver.java new file mode 100644 index 00000000000..1852f716827 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmUuidFromApiResolver.java @@ -0,0 +1,49 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.APIMessage; + +import java.util.List; + +/** + * 从 API 消息中解析关联的 vmInstanceUuid。 + * + *

用于非 VM 直接 API(如 Volume/Nic/快照 API)中提取关联的 VM UUID, + * 以便在 API 成功后触发对应 VM 的元数据更新。

+ * + *

实现类示例

+ *
    + *
  • VolumeToVmResolver:volumeUuid → vmInstanceUuid
  • + *
  • NicToVmResolver:vmNicUuid → vmInstanceUuid
  • + *
  • SnapshotToVmResolver:snapshotUuid → volumeUuid → vmInstanceUuid
  • + *
+ * + *

解析时机

+ *

Resolver 应在 API 执行前 预解析 vmUuid 并缓存在上下文中, + * 因为 API 执行后相关资源可能已被删除(如 APIDeleteVolumeMsg 执行后 VolumeVO 不存在)。

+ * + * @see MetadataImpact + * @see UpdateVmInstanceMetadataMsg + */ +public interface VmUuidFromApiResolver { + + /** + * 判断此 Resolver 是否能处理指定的 API 消息类型。 + * + * @param msg API 消息 + * @return true 表示此 Resolver 可以从该消息中解析 vmUuid + */ + boolean supports(APIMessage msg); + + /** + * 从 API 消息中解析出关联的 vmInstanceUuid 列表。 + * + *

可能返回空列表(如 volume 未挂载到任何 VM)。 + * 可能返回多个 UUID(如批量操作涉及多台 VM)。

+ * + *

此方法应在 API 执行前调用。

+ * + * @param msg API 消息 + * @return 关联的 vmInstanceUuid 列表,不为 null + */ + List resolveVmUuids(APIMessage msg); +} \ No newline at end of file diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 92e76ede2c5..51e3c4f1dee 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -4716,4 +4716,20 @@ public void setMemoryUsage(long memoryUsage) { this.memoryUsage = memoryUsage; } } + + public static class WriteVmInstanceMetadataCmd extends AgentCommand { + public String metadata; + public String metadataPath; + } + + public static class WriteVmInstanceMetadataRsp extends AgentResponse { + } + + public static class ReadVmInstanceMetadataCmd extends AgentCommand { + public String metadataPath; + } + + public static class ReadVmInstanceMetadataRsp extends AgentResponse { + public String metadata; + } } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index 7cd78c36c93..6c845676ca8 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -86,6 +86,9 @@ public interface KVMConstant { String CLEAN_FIRMWARE_FLASH = "/clean/firmware/flash"; String FSTRIM_VM_PATH = "/vm/fstrim"; + String WRITE_VM_INSTANCE_METADATA_PATH = "/vm/metadata/write"; + String READ_VM_INSTANCE_METADATA_PATH = "/vm/metadata/read"; + String ISO_TO = "kvm.isoto"; String ANSIBLE_PLAYBOOK_NAME = "kvm.py"; String ANSIBLE_MODULE_PATH = "ansible/kvm"; diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 23d7b1cfe47..86a19c60b2f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -230,6 +230,8 @@ public class KVMHost extends HostBase implements Host { private String fileDownloadPath; private String fileUploadPath; private String fileDownloadProgressPath; + private String writeVmInstanceMetadataPath; + private String readVmInstanceMetadataPath; public KVMHost(KVMHostVO self, KVMHostContext context) { super(self); @@ -480,6 +482,14 @@ public KVMHost(KVMHostVO self, KVMHostContext context) { ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PROGRESS_PATH); fileDownloadProgressPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.WRITE_VM_INSTANCE_METADATA_PATH); + writeVmInstanceMetadataPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.READ_VM_INSTANCE_METADATA_PATH); + readVmInstanceMetadataPath = ub.build().toString(); } static { @@ -738,6 +748,10 @@ protected void handleLocalMessage(Message msg) { handle((GetFileDownloadProgressMsg) msg); } else if (msg instanceof RestartKvmAgentMsg) { handle((RestartKvmAgentMsg) msg); + } else if (msg instanceof UpdateVmInstanceMetadataOnHypervisorMsg) { + handle((UpdateVmInstanceMetadataOnHypervisorMsg) msg); + } else if (msg instanceof ReadVmInstanceMetadataOnHypervisorMsg) { + handle((ReadVmInstanceMetadataOnHypervisorMsg) msg); } else { super.handleLocalMessage(msg); } @@ -7309,4 +7323,77 @@ public void fail(ErrorCode errorCode) { } }); } + + private void handle(UpdateVmInstanceMetadataOnHypervisorMsg msg) { + inQueue().name(String.format("update-vmInstance-metadata-on-host-%s", self.getUuid())) + .asyncBackup(msg) + .run(chain -> updateVmInstanceMetadata(msg, new NoErrorCompletion(chain) { + @Override + public void done() { + chain.next(); + } + })); + } + + private void updateVmInstanceMetadata(final UpdateVmInstanceMetadataOnHypervisorMsg msg, final NoErrorCompletion completion) { + UpdateVmInstanceMetadataOnHypervisorReply reply = new UpdateVmInstanceMetadataOnHypervisorReply(); + + checkStatus(); + WriteVmInstanceMetadataCmd cmd = new WriteVmInstanceMetadataCmd(); + cmd.metadata = msg.getMetadata(); + cmd.metadataPath = msg.getMetadataPath(); + new Http<>(writeVmInstanceMetadataPath, cmd, WriteVmInstanceMetadataRsp.class).call(new ReturnValueCompletion(msg) { + @Override + public void success(WriteVmInstanceMetadataRsp ret) { + if (!ret.isSuccess()) { + reply.setError(operr("operation error, because:%s", ret.getError())); + } + bus.reply(msg, reply); + completion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + completion.done(); + } + }); + } + + private void handle(ReadVmInstanceMetadataOnHypervisorMsg msg) { + inQueue().name(String.format("readVmInstanceMetadata-on-host-%s", self.getUuid())) + .asyncBackup(msg) + .run(chain -> readVmInstanceMetadata(msg, new NoErrorCompletion(chain) { + @Override + public void done() { + chain.next(); + } + })); + } + + private void readVmInstanceMetadata(final ReadVmInstanceMetadataOnHypervisorMsg msg, final NoErrorCompletion completion) { + checkStatus(); + ReadVmInstanceMetadataOnHypervisorReply reply = new ReadVmInstanceMetadataOnHypervisorReply(); + ReadVmInstanceMetadataCmd cmd = new ReadVmInstanceMetadataCmd(); + cmd.metadataPath = msg.getMetadataPath(); + new Http<>(readVmInstanceMetadataPath, cmd, ReadVmInstanceMetadataRsp.class).call(new ReturnValueCompletion(msg) { + @Override + public void success(ReadVmInstanceMetadataRsp rsp) { + if (!rsp.isSuccess()) { + reply.setError(operr("operation error, because:%s", rsp.getError())); + } + reply.setMetadata(rsp.metadata); + bus.reply(msg, reply); + completion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + completion.done(); + } + }); + } } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java index d4665a86a06..792cd13017e 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.compute.host.VolumeMigrationTargetHostFilter; +import org.zstack.compute.vm.VmGlobalConfig; import org.zstack.core.asyncbatch.While; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.EventFacade; @@ -902,6 +903,8 @@ public void handleLocalMessage(Message msg) { handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) { handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) { + handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg); } else { super.handleLocalMessage(msg); } @@ -3329,4 +3332,49 @@ public void fail(ErrorCode errorCode) { public static class LocalStoragePhysicalCapacityUsage extends PrimaryStorageBase.PhysicalCapacityUsage { public long localStorageUsedSize; } + + private void handle(final UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + // Layer 3: PS-level concurrency control (§4) + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return "update-metadata-on-ps-" + self.getUuid(); + } + + @Override + public int getSyncLevel() { + return VmGlobalConfig.VM_METADATA_PS_MAX_CONCURRENT.value(Integer.class); + } + + @Override + public void run(SyncTaskChain chain) { + doHandleUpdateMetadata(msg); + chain.next(); + } + + @Override + public String getName() { + return "update-metadata-on-ps-" + self.getUuid(); + } + }); + } + + private void doHandleUpdateMetadata(final UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + final String hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + } + + @Override + public void fail(ErrorCode errorCode) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java index 7760e28de93..7e85d562d8c 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java @@ -7,6 +7,8 @@ import org.zstack.header.image.ImageInventory; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.vm.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.*; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply; @@ -121,4 +123,6 @@ public LocalStorageHypervisorBackend(PrimaryStorageVO self) { abstract void handle(CommitVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); abstract void handle(PullVolumeSnapshotOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + abstract void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java index e8d268e518a..7da947744b7 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java @@ -43,10 +43,8 @@ import org.zstack.header.storage.backup.*; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.*; +import org.zstack.header.vm.*; import org.zstack.header.vm.VmInstanceSpec.ImageSpec; -import org.zstack.header.vm.VmInstanceState; -import org.zstack.header.vm.VmInstanceVO; -import org.zstack.header.vm.VmInstanceVO_; import org.zstack.header.volume.*; import org.zstack.identity.AccountManager; import org.zstack.kvm.*; @@ -70,6 +68,7 @@ import static org.zstack.core.Platform.inerr; import static org.zstack.core.Platform.multiErr; import static org.zstack.core.Platform.operr; +import static org.zstack.header.vm.VmInstanceConstant.VM_META_SUFFIX; import static org.zstack.utils.CollectionDSL.list; import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; @@ -3797,4 +3796,31 @@ public void fail(ErrorCode errorCode) { } }); } + + @Override + void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + String installPath = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, msg.getRootVolumeUuid()).select(VolumeVO_.installPath).findValue(); + // /vms_ds/rootVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-829a91b68e794a03865eab8a5918600a/snapshots/f2c31aeede604917aa8cee24848d8bfa.qcow2 + // /vms_ds/rootVolumes/acct-36c27e8ff05c4780bf6d2fa65700f22e/vol-829a91b68e794a03865eab8a5918600a/829a91b68e794a03865eab8a5918600a.qcow2 + + String path = installPath.replaceFirst("^(.+/vol-[^/]+/).*$", "$1"); + String metadataPath = String.format("%s%s", path, VM_META_SUFFIX); + + UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); + umsg.setMetadata(msg.getMetadata()); + umsg.setMetadataPath(metadataPath); + umsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(umsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + if (!r.isSuccess()) { + reply.setError(Platform.operr("failed to update vm[uuid=%s] on hypervisor.", self.getUuid()) + .withCause(r.getError())); + } + bus.reply(msg, reply); + } + }); + } } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java index abe9ac152b6..f179dacfeb9 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java @@ -40,10 +40,9 @@ import org.zstack.header.storage.snapshot.ShrinkVolumeSnapshotOnPrimaryStorageMsg; import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.header.vm.*; import org.zstack.header.vm.VmInstanceSpec.ImageSpec; -import org.zstack.header.vm.VmInstanceState; -import org.zstack.header.vm.VmInstanceVO; -import org.zstack.header.vm.VmInstanceVO_; import org.zstack.header.volume.*; import org.zstack.kvm.*; import org.zstack.storage.primary.*; @@ -131,6 +130,8 @@ protected void handleLocalMessage(Message msg) { handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) { handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) { + handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg); } else { super.handleLocalMessage(msg); } @@ -1924,4 +1925,57 @@ private String getHostUuidFromVolume(String volumeUuid) { return hostUuid; } + + protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + // Layer 3: PS-level concurrency control (§4) + // 同一 MN 上同一 PS 最多 N 个并发元数据写入 + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return "update-metadata-on-ps-" + self.getUuid(); + } + + @Override + public int getSyncLevel() { + return VmGlobalConfig.VM_METADATA_PS_MAX_CONCURRENT.value(Integer.class); + } + + @Override + public void run(SyncTaskChain chain) { + doHandleUpdateMetadata(msg); + chain.next(); + } + + @Override + public String getName() { + return "update-metadata-on-ps-" + self.getUuid(); + } + }); + } + + private void doHandleUpdateMetadata(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + + String hostUuid = getHostUuidFromVolume(msg.getRootVolumeUuid()); + if (hostUuid == null || hostUuid.isEmpty()) { + reply.setError(operr("no host found for volume[uuid:%s]", msg.getRootVolumeUuid())); + bus.reply(msg, reply); + return; + } + + final NfsPrimaryStorageBackend backend = getUsableBackend(); + + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) { + bus.reply(msg, r); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java index 459023d7c17..a19f2d1d38e 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java @@ -7,6 +7,8 @@ import org.zstack.header.image.ImageInventory; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.vm.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.VolumeStats; import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg; import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply; @@ -91,6 +93,8 @@ public interface NfsPrimaryStorageBackend { void updateMountPoint(PrimaryStorageInventory pinv, String clusterUuid, String oldMountPoint, String newMountPoint, Completion completion); + void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + class BitsInfo { private String installPath; private long size; diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java index 93d3d7aab99..4e0dd7309d5 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java @@ -35,10 +35,7 @@ import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; -import org.zstack.header.vm.VmInstanceSpec; -import org.zstack.header.vm.VmInstanceState; -import org.zstack.header.vm.VmInstanceVO; -import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.*; import org.zstack.header.volume.*; import org.zstack.identity.AccountManager; import org.zstack.kvm.*; @@ -2051,4 +2048,23 @@ public void run(MessageReply r) { } }); } + + public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + UpdateVmInstanceMetadataOnHypervisorMsg umsg = new UpdateVmInstanceMetadataOnHypervisorMsg(); + umsg.setMetadata(msg.getMetadata()); + umsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(umsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + if (!r.isSuccess()) { + reply.setError(Platform.operr("failed to update vm[uuid=%s] metadata on hypervisor via host[uuid:%s]", + msg.getVmInstanceUuid(), hostUuid) + .withCause(r.getError())); + } + completion.success(reply); + } + }); + } } diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java new file mode 100644 index 00000000000..1fb295bf5ae --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceAction.java @@ -0,0 +1,113 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RegisterVmInstanceAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.RegisterVmInstanceResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String clusterUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String zoneUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String primaryStorageUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String metadataPath; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.RegisterVmInstanceResult value = res.getResult(org.zstack.sdk.RegisterVmInstanceResult.class); + ret.value = value == null ? new org.zstack.sdk.RegisterVmInstanceResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "POST"; + info.path = "/vm-instances/register"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceResult.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceResult.java new file mode 100644 index 00000000000..49510a84cb9 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmInstanceInventory; + +public class RegisterVmInstanceResult { + public VmInstanceInventory inventory; + public void setInventory(VmInstanceInventory inventory) { + this.inventory = inventory; + } + public VmInstanceInventory getInventory() { + return this.inventory; + } + +} diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java index b7f8cfbc24d..869e7133c26 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java @@ -14,6 +14,8 @@ import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.cloudbus.EventFacade; import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.core.config.GlobalConfig; +import org.zstack.core.config.GlobalConfigDefinition; import org.zstack.core.db.*; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; @@ -27,6 +29,7 @@ import org.zstack.core.trash.TrashType; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; +import org.zstack.core.workflow.ShareFlowChain; import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.core.*; import org.zstack.header.core.trash.CleanTrashResult; @@ -49,16 +52,24 @@ import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageDeletedData; import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageStatusChangedData; import org.zstack.header.storage.snapshot.*; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupRefVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.tag.TagDefinition; import org.zstack.header.vm.*; import org.zstack.header.volume.*; +import org.zstack.resourceconfig.BindResourceConfig; import org.zstack.storage.volume.VolumeUtils; +import org.zstack.tag.SystemTag; +import org.zstack.utils.BeanUtils; import org.zstack.utils.CollectionDSL; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; +import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import javax.persistence.LockModeType; import javax.persistence.TypedQuery; +import java.lang.reflect.Field; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -177,6 +188,8 @@ public void setNewAdded(boolean newAdded) { protected abstract void handle(GetVolumeSnapshotEncryptedOnPrimaryStorageMsg msg); + protected abstract void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg); + public PrimaryStorageBase(PrimaryStorageVO self) { this.self = self; } @@ -935,6 +948,10 @@ protected void handleApiMessage(APIMessage msg) { handle((APICleanUpStorageTrashOnPrimaryStorageMsg) msg); } else if (msg instanceof APIAddStorageProtocolMsg) { handle((APIAddStorageProtocolMsg) msg); + } else if (msg instanceof APIRegisterVmInstanceMsg) { + handle((APIRegisterVmInstanceMsg) msg); + } else if (msg instanceof APIGetVmInstanceMetadataFromPrimaryStorageMsg) { + handle((APIGetVmInstanceMetadataFromPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } @@ -1812,4 +1829,375 @@ protected ImageCacheVO createTemporaryImageCacheFromVolumeSnapshot(ImageInventor private static String getDeduplicateError(String operationName) { return String.format("an other %s task is running, cancel this operation", operationName); } + + private void handle(APIRegisterVmInstanceMsg msg) { + APIRegisterVmInstanceReply event = new APIRegisterVmInstanceReply(msg.getId()); + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("register-vm-from-%s", msg.getMetadataPath()); + } + + @Override + public void run(SyncTaskChain chain) { + registerVmInstance(msg, new ReturnValueCompletion(chain, msg) { + @Override + public void success(VmInstanceInventory vmInstanceInventory) { + event.setInventory(vmInstanceInventory); + bus.publish(event); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + bus.publish(event); + chain.next(); + } + }); + } + + @Override + public String getName() { + return String.format("register-vm-from-%s", msg.getMetadataPath()); + } + }); + } + + private void registerVmInstance(APIRegisterVmInstanceMsg msg, ReturnValueCompletion completion) { + FlowChain chain = new ShareFlowChain(); + chain.setName("register-vm-from-metadata"); + chain.then(new ShareFlow() { + VmMetadata vmMetadata; + VmInstanceInventory vmInstanceInventory; + + @Override + public void setup() { + flow(new NoRollbackFlow() { + String __name__ = "read-metadata"; + + @Override + public void run(FlowTrigger trigger, Map data) { + ReadVmInstanceMetadataOnHypervisorMsg umsg = new ReadVmInstanceMetadataOnHypervisorMsg(); + umsg.setHostUuid(msg.getHostUuid()); + umsg.setMetadataPath(msg.getMetadataPath()); + bus.makeTargetServiceIdByResourceUuid(umsg, HostConstant.SERVICE_ID, msg.getHostUuid()); + bus.send(umsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + if (!r.isSuccess()) { + trigger.fail(operr("failed to update vm[uuid=%s] on hypervisor.", + self.getUuid()).withCause(r.getError())); + return; + } + ReadVmInstanceMetadataOnHypervisorReply reply = r.castReply(); + vmMetadata = JSONObjectUtil.toObject(reply.getMetadata(), VmMetadata.class); + trigger.next(); + } + }); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "register-volume"; + + @Override + public void run(FlowTrigger trigger, Map data) { + List volumesString = vmMetadata.volumeVOs; + + List volumes = new ArrayList<>(); + volumesString.forEach(v -> volumes.add(JSONObjectUtil.toObject(v, VolumeVO.class))); + + List newVolumes = new ArrayList<>(); + volumes.forEach(v -> { + VolumeVO vo = new VolumeVO(); +// vo.setRootImageUuid(vo.getRootImageUuid()); + vo.setAccountUuid(msg.getSession().getAccountUuid()); + vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); + vo.setInstallPath(v.getInstallPath()); + + vo.setCreateDate(v.getCreateDate()); + vo.setDescription(v.getDescription()); + vo.setName(v.getName()); + vo.setSize(v.getSize()); + vo.setActualSize(v.getActualSize()); + vo.setState(v.getState()); + vo.setUuid(v.getUuid()); + vo.setVmInstanceUuid(v.getVmInstanceUuid()); + vo.setType(v.getType()); + vo.setCreateDate(v.getCreateDate()); + vo.setLastOpDate(v.getLastOpDate()); + vo.setDeviceId(v.getDeviceId()); + vo.setStatus(v.getStatus()); + vo.setFormat(v.getFormat()); + vo.setShareable(v.isShareable()); + vo.setVolumeQos(v.getVolumeQos()); + vo.setLastDetachDate(v.getLastDetachDate()); + vo.setLastVmInstanceUuid(v.getLastVmInstanceUuid()); + vo.setLastAttachDate(v.getLastAttachDate()); + vo.setProtocol(v.getProtocol()); + newVolumes.add(vo); + }); + dbf.persistCollection(newVolumes); + trigger.next(); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "register-snapshot"; + + @Override + public void run(FlowTrigger trigger, Map data) { + // 快照 + vmMetadata.volumeSnapshots.forEach((volumeUuid, snapshotList) -> { + // 一个 volume 有多个快照树 + // key = treeuuid + // value = snapshosts + Map> snapshotsByTreeUuid = new HashMap<>(); + snapshotList.forEach(snapshot -> { + VolumeSnapshotInventory inv = JSONObjectUtil.toObject(snapshot, VolumeSnapshotInventory.class); + if (snapshotsByTreeUuid.containsKey(inv.getTreeUuid())) { + snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); + } else { + snapshotsByTreeUuid.put(inv.getTreeUuid(), new ArrayList<>()); + snapshotsByTreeUuid.get(inv.getTreeUuid()).add(inv); + } + }); + + // 遍历每一颗树 + snapshotsByTreeUuid.forEach((treeUuid, snapshots) -> { + //构建快照树 + VolumeSnapshotTree tree = VolumeSnapshotTree.fromInventories(snapshots); + // 层级遍历 快照 + List levelOrderTraversals = tree.levelOrderTraversal(); + // 判断当前树有没有 latest 节点 + boolean treeIsCurrent = levelOrderTraversals.stream().anyMatch(VolumeSnapshotInventory::isLatest); + + // 先创建快照树,VolumeSnapshotVO 外键依赖 VolumeSnapshotTreeVO + VolumeSnapshotTreeVO newTree = new VolumeSnapshotTreeVO(); + newTree.setCurrent(treeIsCurrent); + newTree.setVolumeUuid(volumeUuid); + newTree.setUuid(treeUuid); + newTree.setStatus(VolumeSnapshotTreeStatus.Completed); + dbf.persist(newTree); + + // 按照层级遍历的快照构建VolumeSnapshotTreeVO + levelOrderTraversals.forEach(snapshot -> { + VolumeSnapshotVO vo = new VolumeSnapshotVO(); + vo.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); + vo.setPrimaryStorageInstallPath(snapshot.getPrimaryStorageInstallPath()); + + vo.setName(snapshot.getName()); + vo.setCreateDate(snapshot.getCreateDate()); + vo.setDescription(snapshot.getDescription()); + vo.setLastOpDate(snapshot.getLastOpDate()); + vo.setParentUuid(snapshot.getParentUuid()); + vo.setState(VolumeSnapshotState.valueOf(snapshot.getState())); + vo.setType(snapshot.getType()); + vo.setVolumeUuid(snapshot.getVolumeUuid()); + vo.setFormat(snapshot.getFormat()); + vo.setUuid(snapshot.getUuid()); + vo.setStatus(VolumeSnapshotStatus.valueOf(snapshot.getStatus())); + vo.setLatest(snapshot.isLatest()); + vo.setSize(snapshot.getSize()); + vo.setVolumeType(snapshot.getVolumeType()); + vo.setTreeUuid(snapshot.getTreeUuid()); + vo.setDistance(snapshot.getDistance()); + dbf.persist(vo); + }); + }); + }); + + // 快照组 + List newGroups = new ArrayList<>(); + vmMetadata.volumeSnapshotGroupVO.forEach(group -> { + VolumeSnapshotGroupVO vo = JSONObjectUtil.toObject(group, VolumeSnapshotGroupVO.class); + vo.setAccountUuid(msg.getSession().getAccountUuid()); + newGroups.add(vo); + }); + dbf.persistCollection(newGroups); + + // 快照组ref + List newGroupRefs = new ArrayList<>(); + vmMetadata.volumeSnapshotGroupRefVO.forEach(group -> { + VolumeSnapshotGroupRefVO vo = JSONObjectUtil.toObject(group, VolumeSnapshotGroupRefVO.class); + newGroupRefs.add(vo); + }); + dbf.persistCollection(newGroupRefs); + + trigger.next(); + } + }); + + flow(new NoRollbackFlow() { + String __name__ = "register-vmInstance"; + + @Override + public void run(FlowTrigger trigger, Map data) { + VmInstanceVO metaVm = JSONObjectUtil.toObject(vmMetadata.vmInstanceVO, VmInstanceVO.class); + VmInstanceVO newVm = new VmInstanceVO(); + + newVm.setClusterUuid(msg.getClusterUuid()); + newVm.setHostUuid(msg.getHostUuid()); + // 寻找有没有cache的tag lv 构建imageCache +// newVm.setImageUuid(); + + newVm.setUuid(metaVm.getUuid()); + newVm.setName(metaVm.getName()); + newVm.setDescription(metaVm.getDescription()); + newVm.setType(metaVm.getType()); + newVm.setHypervisorType(metaVm.getHypervisorType()); + newVm.setCreateDate(metaVm.getCreateDate()); + newVm.setLastOpDate(metaVm.getLastOpDate()); + newVm.setState(metaVm.getState()); + newVm.setRootVolumeUuid(metaVm.getRootVolumeUuid()); + newVm.setInternalId(metaVm.getInternalId()); + newVm.setCpuNum(metaVm.getCpuNum()); + newVm.setCpuSpeed(metaVm.getCpuSpeed()); + newVm.setMemorySize(metaVm.getMemorySize()); + newVm.setReservedMemorySize(metaVm.getReservedMemorySize()); + newVm.setAllocatorStrategy(metaVm.getAllocatorStrategy()); + newVm.setPlatform(metaVm.getPlatform()); + newVm.setArchitecture(metaVm.getArchitecture()); + newVm.setGuestOsType(metaVm.getGuestOsType()); + dbf.persist(newVm); + vmInstanceInventory = VmInstanceInventory.valueOf(newVm); + trigger.next(); +// List vmSystemTags = vmMetadata.vmSystemTags; +// List vmResourceConfigs = vmMetadata.vmResourceConfigs; +// +// try { +// List systemTags = getResourceSystemTagFromSystem(VmInstanceVO.class.getSimpleName()); +// List resourceConfigs = getResourceConfigFromSystem(VmInstanceVO.class.getSimpleName()); +// +// List tagVOS = new ArrayList<>(); +// vmSystemTags.forEach(tag -> { +// List info = asList(tag.split("_")); +// String t = info.get(0); +// Boolean inherent = Boolean.valueOf(info.get(1)); +// String type = info.get(2); +// systemTags.forEach(it -> { +// if (!it.isMatch(t)) { +// return; +// } +// SystemTagVO vo = new SystemTagVO(); +// vo.setTag(t); +// vo.setType(TagType.valueOf(type)); +// vo.setInherent(inherent); +// vo.setResourceType(VmInstanceVO.class.getSimpleName()); +// vo.setResourceUuid(newVm.getUuid()); +// tagVOS.add(vo); +// }); +// }); +// +// List configVOS = new ArrayList<>(); +// vmResourceConfigs.forEach(tag -> { +// List info = asList(tag.split("_")); +// String identity = info.get(0); +// String value = info.get(1); +// resourceConfigs.forEach(it -> { +// if (it.getIdentity() == identity) { +// return; +// } +// ResourceConfigVO vo = new ResourceConfigVO(); +// vo.setCategory(identity); +// vo.setName(identity); +// vo.setValue(value); +// vo.setResourceType(VmInstanceVO.class.getSimpleName()); +// vo.setResourceUuid(newVm.getUuid()); +// configVOS.add(vo); +// }); +// }); +// } catch (IllegalAccessException | InstantiationException e) { +// throw new RuntimeException(e); +// } + } + }); + + done(new FlowDoneHandler(completion) { + @Override + public void handle(Map data) { + completion.success(vmInstanceInventory); + } + }); + + error(new FlowErrorHandler(msg) { + @Override + public void handle(ErrorCode errCode, Map data) { + completion.fail(errCode); + } + }); + } + }).start(); + } + + private List getResourceSystemTagFromSystem(String resourceType) throws IllegalAccessException, InstantiationException { + List systemTags = new ArrayList<>(); + + Set> classes = BeanUtils.reflections.getTypesAnnotatedWith(TagDefinition.class); + for (Class clazz : classes) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (!SystemTag.class.isAssignableFrom(field.getType())) { + continue; + } + + SystemTag systemTag = (SystemTag) field.get(clazz.newInstance()); + + if (resourceType.equals(systemTag.getResourceClass().getName())) { + systemTags.add(systemTag); + } + } + } + return systemTags; + } + + private List getResourceConfigFromSystem(String resourceType) throws IllegalAccessException, InstantiationException { + List globalConfigs = new ArrayList<>(); + + Set> classes = BeanUtils.reflections.getTypesAnnotatedWith(GlobalConfigDefinition.class); + for (Class clazz : classes) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (!GlobalConfig.class.isAssignableFrom(field.getType())) { + continue; + } + GlobalConfig globalConfig = (GlobalConfig) field.get(clazz.newInstance()); + + BindResourceConfig bindResourceConfig = field.getAnnotation(BindResourceConfig.class); + if (bindResourceConfig == null) { + continue; + } + + List bindResourceConfigs = Arrays.stream(bindResourceConfig.value()).map(Class::getName).collect(Collectors.toList()); + + if (bindResourceConfigs.contains(resourceType)) { + globalConfigs.add(globalConfig); + } + } + } + + return globalConfigs; + } + + private void handle(APIGetVmInstanceMetadataFromPrimaryStorageMsg msg) { + APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply(); + + GetVmInstanceMetadataFromPrimaryStorageMsg gmsg = new GetVmInstanceMetadataFromPrimaryStorageMsg(); + gmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); + bus.makeTargetServiceIdByResourceUuid(gmsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); + + bus.send(gmsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply r) { + if (!r.isSuccess()) { + reply.setError(r.getError()); + bus.reply(msg, reply); + return; + } + GetVmInstanceMetadataFromPrimaryStorageReply re = r.castReply(); + reply.setVmInstanceMetadata(re.getVmInstanceMetadata()); + bus.reply(msg, reply); + } + }); + } } diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 07c05b73b9e..5ddb04eac93 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -27406,6 +27406,33 @@ abstract class ApiHelper { } + def registerVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.RegisterVmInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.RegisterVmInstanceAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def reimageVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReimageVmInstanceAction.class) Closure c) { def a = new org.zstack.sdk.ReimageVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid diff --git a/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy b/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy index 94fc178245d..fcfb5a8ff78 100755 --- a/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy +++ b/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy @@ -680,5 +680,15 @@ class KVMSimulator implements Simulator { spec.simulator(KVMConstant.KVM_UPDATE_HOSTNAME_PATH) { return new UpdateHostnameRsp() } + + spec.simulator(KVMConstant.WRITE_VM_INSTANCE_METADATA_PATH) { HttpEntity e -> + return new WriteVmInstanceMetadataRsp() + } + + spec.simulator(KVMConstant.READ_VM_INSTANCE_METADATA_PATH) { HttpEntity e -> + def rsp = new ReadVmInstanceMetadataRsp() + rsp.metadata = "{\"vmInstanceVO\":\"{\\\"vmNics\\\":[{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"l3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"mac\\\":\\\"fa:81:16:b2:32:00\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"deviceId\\\":0,\\\"internalName\\\":\\\"vnic1.0\\\",\\\"driverType\\\":\\\"virtio\\\",\\\"type\\\":\\\"VNIC\\\",\\\"state\\\":\\\"enable\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"usedIps\\\":[],\\\"uuid\\\":\\\"a77234a5a45a4a7caca46d01d746f41f\\\",\\\"resourceType\\\":\\\"VmNicVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmNicVO\\\"}],\\\"allVolumes\\\":[{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceName\\\":\\\"ROOT-for-vmName\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"},{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}],\\\"vmCdRoms\\\":[{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"deviceId\\\":0,\\\"name\\\":\\\"vm-77bc3074f5f4438c836ce6c56bc5a4aa-cdRom\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"uuid\\\":\\\"e8a57f5b8c834573b4da822b672740e4\\\",\\\"resourceName\\\":\\\"vm-77bc3074f5f4438c836ce6c56bc5a4aa-cdRom\\\",\\\"resourceType\\\":\\\"VmCdRomVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.cdrom.VmCdRomVO\\\"}],\\\"name\\\":\\\"vmName\\\",\\\"zoneUuid\\\":\\\"d71de3f6981d46c9a2be43e5fcf31021\\\",\\\"clusterUuid\\\":\\\"29f13acb820d4f7f8cd3593b79b742e5\\\",\\\"imageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"hostUuid\\\":\\\"e99debc09c5845fb8ed682320117f4ce\\\",\\\"internalId\\\":1,\\\"lastHostUuid\\\":\\\"e99debc09c5845fb8ed682320117f4ce\\\",\\\"rootVolumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"defaultL3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"type\\\":\\\"UserVm\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"cpuNum\\\":1,\\\"cpuSpeed\\\":0,\\\"memorySize\\\":1073741824,\\\"reservedMemorySize\\\":0,\\\"platform\\\":\\\"Linux\\\",\\\"architecture\\\":\\\"x86_64\\\",\\\"guestOsType\\\":\\\"CentOS\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"state\\\":\\\"Running\\\",\\\"uuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceName\\\":\\\"vmName\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmInstanceVO\\\"}\",\"vmSystemTags\":[\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"38a9b4bd1b8b3dfa829d582aafb2ec25\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"syncPorts::77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:45 AM\\\"}\",\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"3e984cdb5edb47559a3f907e1d49bfcc\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"additionalQmp\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"85237d3a06133523bd84669349040ec5\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"vmPriority::Normal\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"b7c5d5e94ba13159ab2c8c65c1d7bc29\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"vmSystemSerialNumber::8ed14f00-50bb-4e9e-9448-e92c0f67e1e1\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\",\"{\\\"inherent\\\":false,\\\"uuid\\\":\\\"d5019730aeba3e57b2f1a3e8d74d0cbc\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"tag\\\":\\\"ha::None\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"vmResourceConfigs\":[\"{\\\"uuid\\\":\\\"8d2f9937a28846aba03fded826c10c73\\\",\\\"resourceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"resourceType\\\":\\\"VmInstanceVO\\\",\\\"name\\\":\\\"nicMultiQueueNum\\\",\\\"description\\\":\\\"default num of queues on virtio nic\\\",\\\"category\\\":\\\"vm\\\",\\\"value\\\":\\\"1\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"volumeVOs\":[\"{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"ROOT-for-vmName\\\",\\\"description\\\":\\\"Root volume for VM[uuid:77bc3074f5f4438c836ce6c56bc5a4aa]\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"rootImageUuid\\\":\\\"575591e021b446e4b465e981da3a8d1b\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"type\\\":\\\"Root\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":0,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceName\\\":\\\"ROOT-for-vmName\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":3,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":2,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\",\"{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false,\\\"shadow\\\":{\\\"name\\\":\\\"volumeName1null\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"installPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/43436624dc714282913e0a141246629e\\\",\\\"type\\\":\\\"Data\\\",\\\"status\\\":\\\"Ready\\\",\\\"size\\\":1073741824,\\\"actualSize\\\":0,\\\"deviceId\\\":1,\\\"format\\\":\\\"qcow2\\\",\\\"state\\\":\\\"Enabled\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"isShareable\\\":false},\\\"uuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceName\\\":\\\"volumeName1null\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.volume.VolumeVO\\\"}\"],\"volumeSystemTags\":{\"b7290c15276b4700af2c1b108b2b62e1\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"b9874ec02b583538a5603e7eec8c5b69\\\",\\\"resourceUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000f59f934d14a68\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"8d1e76eca52647f5a4544b9ff2d370de\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"96cb4b006708387b8318f0fd6ae6ab8b\\\",\\\"resourceUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000faad0c9ca4231\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:48 AM\\\"}\"],\"ae9f28cb5055498e8661793d204208ba\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"5ceacd06bf753b0c8abe5bcef9b5a894\\\",\\\"resourceUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000fc4ffeaab6e71\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"db8251e870b14d60ace863a7598cce8b\":[\"{\\\"inherent\\\":true,\\\"uuid\\\":\\\"d53865baa675373a9bf07a6f501eab41\\\",\\\"resourceUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"resourceType\\\":\\\"VolumeVO\\\",\\\"tag\\\":\\\"kvm::volume::0x000fad154165d205\\\",\\\"type\\\":\\\"System\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\"]},\"volumeResourceConfigs\":{\"b7290c15276b4700af2c1b108b2b62e1\":[],\"8d1e76eca52647f5a4544b9ff2d370de\":[],\"ae9f28cb5055498e8661793d204208ba\":[],\"db8251e870b14d60ace863a7598cce8b\":[]},\"vmNicVOs\":[\"{\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"l3NetworkUuid\\\":\\\"28d3a9c8e54c48f290ab4f9e52bbb006\\\",\\\"mac\\\":\\\"fa:81:16:b2:32:00\\\",\\\"hypervisorType\\\":\\\"KVM\\\",\\\"deviceId\\\":0,\\\"internalName\\\":\\\"vnic1.0\\\",\\\"driverType\\\":\\\"virtio\\\",\\\"type\\\":\\\"VNIC\\\",\\\"state\\\":\\\"enable\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:46 AM\\\",\\\"usedIps\\\":[],\\\"uuid\\\":\\\"a77234a5a45a4a7caca46d01d746f41f\\\",\\\"resourceType\\\":\\\"VmNicVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.vm.VmNicVO\\\"}\"],\"vmNicSystemTags\":{\"a77234a5a45a4a7caca46d01d746f41f\":[]},\"vmNicResourceConfigs\":{\"a77234a5a45a4a7caca46d01d746f41f\":[]},\"volumeSnapshots\":{\"b7290c15276b4700af2c1b108b2b62e1\":[\"{\\\"uuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\",\"{\\\"uuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"parentUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"treeUuid\\\":\\\"f8042fb57bb04ebcb0f01bab2abeb5dd\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\"],\"8d1e76eca52647f5a4544b9ff2d370de\":[\"{\\\"uuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":0,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"treeUuid\\\":\\\"d4a030087ed3407894c393ee81f0bc3b\\\",\\\"parentUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":0,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\"],\"ae9f28cb5055498e8661793d204208ba\":[\"{\\\"uuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\",\"{\\\"uuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"treeUuid\\\":\\\"055b80b0727e4117b246b1b29f2d58b6\\\",\\\"parentUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\"],\"db8251e870b14d60ace863a7598cce8b\":[\"{\\\"uuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":true,\\\"size\\\":1,\\\"distance\\\":4,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\"}\",\"{\\\"uuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":2,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\"}\",\"{\\\"uuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":1,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:51 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:53 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\"}\",\"{\\\"uuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"type\\\":\\\"Hypervisor\\\",\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"treeUuid\\\":\\\"1c0773fa98f4465b8e535ba3c00dc039\\\",\\\"parentUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"primaryStorageUuid\\\":\\\"e121a11157bb4746ad3c8d56c3760a3e\\\",\\\"primaryStorageInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"format\\\":\\\"qcow2\\\",\\\"latest\\\":false,\\\"size\\\":1,\\\"distance\\\":3,\\\"state\\\":\\\"Enabled\\\",\\\"status\\\":\\\"Ready\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"backupStorageRefs\\\":[],\\\"groupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\"}\"]},\"volumeSnapshotGroupVO\":[\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}],\\\"uuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\",\"{\\\"snapshotCount\\\":4,\\\"name\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"vmInstanceUuid\\\":\\\"77bc3074f5f4438c836ce6c56bc5a4aa\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeSnapshotRefs\\\":[{\\\"volumeSnapshotUuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"},{\\\"volumeSnapshotUuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}],\\\"uuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"resourceName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"resourceType\\\":\\\"VolumeSnapshotGroupVO\\\",\\\"concreteResourceType\\\":\\\"org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO\\\"}\"],\"volumeSnapshotGroupRefVO\":[\"{\\\"volumeSnapshotUuid\\\":\\\"04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"43436624dc714282913e0a141246629e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"7832daf63d9b41d68bd1460c20ed0e0a\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"79caace79a1048d58ea7c0b38815bbd0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/61e2ada0170142bb8b303910a27690aa\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a31c3de68ce246538e982e0e5c7d2d73\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a35b7ae1616a4974b2f80654c5527fbb\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"a70bb2be871644b6ad12ac8d6e9524d0\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"7648a93930db473785b0abc0e0716c1a\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":1,\\\"volumeUuid\\\":\\\"db8251e870b14d60ace863a7598cce8b\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/791f523bc99a4f08bd70b5a59d8ed5c8\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:56 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:49 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"aefbe47465c047d1b118321c34425869\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/69e85ac72fea4263a55cbcd21785006e\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"b5d771aa83584c9c88d9b84147dfc9ad\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"6db066c890d141008e8ff18bd5940d77\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:54 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"b711f22ad5c045b6ad1d770d4f301d05\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":3,\\\"volumeUuid\\\":\\\"ae9f28cb5055498e8661793d204208ba\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/1aaa92b2d1eb4c36bb8951b8e1521b34\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"bc5ab54cb3d04635923a2a9d0b5fc73f\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"d2d486455d6c472cbb7391958edebea5\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":0,\\\"volumeUuid\\\":\\\"8d1e76eca52647f5a4544b9ff2d370de\\\",\\\"volumeName\\\":\\\"ROOT-for-vmName\\\",\\\"volumeType\\\":\\\"Root\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/04ef6d31675f4ba5816b104920dc3e2c\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-ROOT-for-vmName\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:57 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:47 AM\\\"}\",\"{\\\"volumeSnapshotUuid\\\":\\\"bcc3ed8070984cd691c62f421aeaa44d\\\",\\\"volumeSnapshotGroupUuid\\\":\\\"01171364e6704dbe83c36167de52d719\\\",\\\"snapshotDeleted\\\":false,\\\"deviceId\\\":2,\\\"volumeUuid\\\":\\\"b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeName\\\":\\\"volumeName1null\\\",\\\"volumeType\\\":\\\"Data\\\",\\\"volumeSnapshotInstallPath\\\":\\\"sharedblock://e121a11157bb4746ad3c8d56c3760a3e/b7290c15276b4700af2c1b108b2b62e1\\\",\\\"volumeSnapshotName\\\":\\\"group-8d1e76eca52647f5a4544b9ff2d370de-volumeName1null\\\",\\\"createDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"lastOpDate\\\":\\\"Jan 28, 2026 4:15:52 AM\\\",\\\"volumeLastAttachDate\\\":\\\"Jan 28, 2026 4:15:50 AM\\\"}\"],\"volumeSnapshotReferenceVO\":{},\"volumeSnapshotReferenceTreeVO\":{},\"EncryptedResourceKeyRefVO\":{}}" + return rsp + } } }