From d23bf5cafd77a75d8356396e013bcd98b34ef8ae Mon Sep 17 00:00:00 2001 From: "tao.gan" Date: Mon, 23 Mar 2026 00:26:42 +0800 Subject: [PATCH] [vm]: add MetadataImpact APIImpact Resolves: ZSV-11559 Change-Id: I6b6a6378627264646d6a76726762736e77787373 --- .../compute/vm/VmExpungeMetadataFlow.java | 90 ++++++ .../org/zstack/compute/vm/VmGlobalConfig.java | 85 ++++++ .../org/zstack/compute/vm/VmSystemTags.java | 4 + conf/db/upgrade/V5.0.0__schema.sql | 24 ++ conf/globalConfig/vm.xml | 232 ++++++++++++++ conf/persistence.xml | 2 + conf/serviceConfig/primaryStorage.xml | 5 +- conf/serviceConfig/vmInstance.xml | 12 + conf/springConfigXml/VmInstanceManager.xml | 1 + .../APIGetPhysicalMachineBlockDevicesMsg.java | 2 +- ...InstanceMetadataFromPrimaryStorageMsg.java | 35 +++ ...adataFromPrimaryStorageMsgDoc_zh_cn.groovy | 58 ++++ ...stanceMetadataFromPrimaryStorageReply.java | 26 ++ ...ataFromPrimaryStorageReplyDoc_zh_cn.groovy | 32 ++ ...VmInstanceMetadataOnPrimaryStorageMsg.java | 43 +++ ...InstanceMetadataOnPrimaryStorageReply.java | 6 + ...InstanceMetadataFromPrimaryStorageMsg.java | 44 +++ ...stanceMetadataFromPrimaryStorageReply.java | 15 + .../primary/ReadVmInstanceMetadataMsg.java | 21 ++ .../primary/ReadVmInstanceMetadataReply.java | 15 + ...eVolumeBackingFileOnPrimaryStorageMsg.java | 70 +++++ ...olumeBackingFileOnPrimaryStorageReply.java | 15 + ...InstanceMetadataFromPrimaryStorageMsg.java | 25 ++ ...stanceMetadataFromPrimaryStorageReply.java | 18 ++ .../storage/primary/VmMetadataScanEntry.java | 94 ++++++ .../snapshot/APIDeleteVolumeSnapshotMsg.java | 2 + .../APIRevertVolumeFromSnapshotMsg.java | 2 + .../snapshot/APIShrinkVolumeSnapshotMsg.java | 2 + .../snapshot/APIUpdateVolumeSnapshotMsg.java | 2 + .../APIDeleteVolumeSnapshotGroupMsg.java | 2 + .../APIRevertVmFromSnapshotGroupMsg.java | 2 + .../APIUngroupVolumeSnapshotGroupMsg.java | 2 + .../APIUpdateVolumeSnapshotGroupMsg.java | 2 + .../header/tag/APICreateSystemTagMsg.java | 2 + .../header/tag/APICreateSystemTagsMsg.java | 2 + .../zstack/header/tag/APIDeleteTagMsg.java | 2 + .../header/tag/APIUpdateSystemTagMsg.java | 2 + .../vm/APIAttachIsoToVmInstanceMsg.java | 2 + .../header/vm/APIAttachL3NetworkToVmMsg.java | 2 + .../header/vm/APIAttachVmNicToVmMsg.java | 2 + .../vm/APIChangeInstanceOfferingMsg.java | 2 + .../header/vm/APIChangeVmNicNetworkMsg.java | 2 + .../header/vm/APIChangeVmNicStateMsg.java | 2 + ...ertTemplatedVmInstanceToVmInstanceMsg.java | 2 + ...ertVmInstanceToTemplatedVmInstanceMsg.java | 2 + .../zstack/header/vm/APICreateVmNicMsg.java | 2 + .../header/vm/APIDeleteVmBootModeMsg.java | 2 + .../vm/APIDeleteVmConsolePasswordMsg.java | 2 + .../header/vm/APIDeleteVmHostnameMsg.java | 2 + .../zstack/header/vm/APIDeleteVmNicMsg.java | 2 + .../header/vm/APIDeleteVmSshKeyMsg.java | 2 + .../header/vm/APIDeleteVmStaticIpMsg.java | 2 + .../header/vm/APIDestroyVmInstanceMsg.java | 2 + .../vm/APIDetachIsoFromVmInstanceMsg.java | 2 + .../vm/APIDetachL3NetworkFromVmMsg.java | 2 + .../header/vm/APIExpungeVmInstanceMsg.java | 2 + .../org/zstack/header/vm/APIMigrateVmMsg.java | 2 + .../header/vm/APIRecoverVmInstanceMsg.java | 2 + .../header/vm/APIReimageVmInstanceMsg.java | 2 + .../zstack/header/vm/APISetVmBootModeMsg.java | 2 + .../header/vm/APISetVmBootOrderMsg.java | 2 + .../header/vm/APISetVmBootVolumeMsg.java | 2 + .../header/vm/APISetVmClockTrackMsg.java | 2 + .../header/vm/APISetVmConsolePasswordMsg.java | 2 + .../zstack/header/vm/APISetVmHostnameMsg.java | 2 + .../header/vm/APISetVmQxlMemoryMsg.java | 2 + .../header/vm/APISetVmSoundTypeMsg.java | 2 + .../zstack/header/vm/APISetVmSshKeyMsg.java | 2 + .../zstack/header/vm/APISetVmStaticIpMsg.java | 2 + .../header/vm/APIUpdateVmInstanceMsg.java | 2 + .../header/vm/APIUpdateVmNicDriverMsg.java | 2 + .../header/vm/APIUpdateVmPriorityMsg.java | 2 + .../org/zstack/header/vm/VmInstanceState.java | 4 +- .../header/vm/cdrom/APICreateVmCdRomMsg.java | 2 + .../APISetVmInstanceDefaultCdRomMsg.java | 2 + .../header/vm/cdrom/APIUpdateVmCdRomMsg.java | 2 + .../APICleanupVmInstanceMetadataEvent.java | 53 ++++ ...nupVmInstanceMetadataEventDoc_zh_cn.groovy | 42 +++ .../APICleanupVmInstanceMetadataMsg.java | 34 +++ ...eanupVmInstanceMetadataMsgDoc_zh_cn.groovy | 58 ++++ ...InstanceMetadataFromPrimaryStorageMsg.java | 37 +++ ...adataFromPrimaryStorageMsgDoc_zh_cn.groovy | 58 ++++ ...stanceMetadataFromPrimaryStorageReply.java | 23 ++ ...ataFromPrimaryStorageReplyDoc_zh_cn.groovy | 29 ++ ...PIRegisterVmInstanceFromMetadataEvent.java | 46 +++ ...mInstanceFromMetadataEventDoc_zh_cn.groovy | 38 +++ .../APIRegisterVmInstanceFromMetadataMsg.java | 102 +++++++ ...rVmInstanceFromMetadataMsgDoc_zh_cn.groovy | 112 +++++++ .../APIUpdateVmInstanceMetadataEvent.java | 19 ++ ...ateVmInstanceMetadataEventDoc_zh_cn.groovy | 23 ++ .../APIUpdateVmInstanceMetadataMsg.java | 35 +++ ...pdateVmInstanceMetadataMsgDoc_zh_cn.groovy | 56 ++++ .../header/vm/metadata/MetadataImpact.java | 28 ++ .../header/vm/metadata/ResourceMetadata.java | 8 + .../metadata/UpdateVmInstanceMetadataMsg.java | 26 ++ ...VmInstanceMetadataOnPrimaryStorageMsg.java | 98 ++++++ ...InstanceMetadataOnPrimaryStorageReply.java | 6 + .../UpdateVmInstanceMetadataReply.java | 15 + .../metadata/VmInstanceMetadataConstants.java | 10 + .../vm/metadata/VmInstanceMetadataDTO.java | 15 + .../vm/metadata/VmMetadataCategory.java | 7 + .../vm/metadata/VmMetadataConstants.java | 25 ++ .../vm/metadata/VmMetadataDirtyService.java | 29 ++ .../header/vm/metadata/VmMetadataDirtyVO.java | 121 ++++++++ .../vm/metadata/VmMetadataDirtyVO_.java | 18 ++ .../header/vm/metadata/VmMetadataErrors.java | 28 ++ .../vm/metadata/VmMetadataFingerprintVO.java | 88 ++++++ .../VmMetadataPathBuildExtensionPoint.java | 7 + ...MetadataPathReplacementExtensionPoint.java | 44 +++ ...MetadataResourcePersistExtensionPoint.java | 11 + .../vm/metadata/VmUuidFromApiResolver.java | 11 + .../vm/metadata/VolumeResourceMetadata.java | 8 + .../volume/APIAttachDataVolumeToVmMsg.java | 2 + .../volume/APIChangeVolumeStateMsg.java | 2 + .../APICreateVolumeSnapshotGroupMsg.java | 2 + .../volume/APICreateVolumeSnapshotMsg.java | 2 + .../header/volume/APIDeleteDataVolumeMsg.java | 2 + .../volume/APIDetachDataVolumeFromVmMsg.java | 2 + .../volume/APIExpungeDataVolumeMsg.java | 2 + .../header/volume/APIFlattenVolumeMsg.java | 2 + .../volume/APIRecoverDataVolumeMsg.java | 2 + .../header/volume/APISyncVolumeSizeMsg.java | 2 + .../volume/APIUndoSnapshotCreationMsg.java | 2 + .../header/volume/APIUpdateVolumeMsg.java | 2 + .../APILocalStorageMigrateVolumeMsg.java | 2 + .../primary/local/LocalStorageBase.java | 193 ++++++++++++ .../local/LocalStorageHypervisorBackend.java | 16 + .../primary/local/LocalStorageKvmBackend.java | 164 +++++++++- .../primary/local/LocalStorageSimulator.java | 38 +++ .../local/LocalStorageSimulatorConfig.java | 5 + .../LocalStorageVmMetadataExtension.java | 127 ++++++++ .../primary/nfs/NfsPrimaryStorage.java | 178 ++++++++++- .../primary/nfs/NfsPrimaryStorageBackend.java | 12 + .../nfs/NfsPrimaryStorageKVMBackend.java | 173 ++++++++++- .../NfsPrimaryStorageKVMBackendCommands.java | 48 +++ .../primary/nfs/NfsVmMetadataExtension.java | 90 ++++++ .../APIDeleteResourceConfigMsg.java | 2 + .../APIUpdateResourceConfigMsg.java | 2 + .../sdk/CleanupVmInstanceMetadataAction.java | 101 +++++++ .../sdk/CleanupVmInstanceMetadataResult.java | 30 ++ ...tanceMetadataFromPrimaryStorageAction.java | 95 ++++++ ...tanceMetadataFromPrimaryStorageResult.java | 14 + .../RegisterVmInstanceFromMetadataAction.java | 119 ++++++++ .../RegisterVmInstanceFromMetadataResult.java | 14 + ...tanceMetadataFromPrimaryStorageAction.java | 95 ++++++ ...tanceMetadataFromPrimaryStorageResult.java | 14 + .../sdk/UpdateVmInstanceMetadataAction.java | 100 +++++++ .../sdk/UpdateVmInstanceMetadataResult.java | 7 + .../storage/primary/PrimaryStorageBase.java | 73 +++++ .../java/org/zstack/testlib/ApiHelper.groovy | 283 +++++++++++++----- .../zstack/testlib/LocalStorageSpec.groovy | 18 ++ .../testlib/NfsPrimaryStorageSpec.groovy | 16 + 152 files changed, 4415 insertions(+), 84 deletions(-) create mode 100644 compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java create mode 100644 conf/db/upgrade/V5.0.0__schema.sql create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ReadVmInstanceMetadataReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java create mode 100644 header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java create mode 100644 plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java create mode 100644 plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java diff --git a/compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java new file mode 100644 index 00000000000..cf65bb0bcd9 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java @@ -0,0 +1,90 @@ +package org.zstack.compute.vm; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.core.db.Q; +import org.zstack.header.core.workflow.FlowTrigger; +import org.zstack.header.core.workflow.NoRollbackFlow; +import org.zstack.header.message.MessageReply; +import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.storage.primary.PrimaryStorageConstant; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.storage.primary.PrimaryStorageVO_; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint; +import org.zstack.header.volume.VolumeVO; +import org.zstack.header.volume.VolumeVO_; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Map; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +public class VmExpungeMetadataFlow extends NoRollbackFlow { + private static final CLogger logger = Utils.getLogger(VmExpungeMetadataFlow.class); + + @Autowired + private CloudBus bus; + @Autowired + private PluginRegistry pluginRgty; + + @Override + public void run(FlowTrigger trigger, Map data) { + final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + if (spec == null || spec.getVmInventory() == null) { + logger.warn("[MetadataExpunge] missing VmInstanceSpec or VmInventory, skip metadata cleanup"); + trigger.next(); + return; + } + + final String vmUuid = spec.getVmInventory().getUuid(); + + String rootVolumeUuid = spec.getVmInventory().getRootVolumeUuid(); + if (rootVolumeUuid == null) { + logger.debug(String.format("[MetadataExpunge] vm[uuid:%s] has no root volume, skipping metadata cleanup", vmUuid)); + trigger.next(); + return; + } + + String psUuid = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, rootVolumeUuid).select(VolumeVO_.primaryStorageUuid).findValue(); + if (psUuid == null) { + logger.debug(String.format("[MetadataExpunge] vm[uuid:%s] root volume[uuid:%s] has no primaryStorageUuid, " + + "skipping metadata cleanup", vmUuid, rootVolumeUuid)); + trigger.next(); + return; + } + + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, psUuid).findValue(); + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + if (ext == null) { + trigger.next(); + return; + } + String metadataPath = ext.buildVmMetadataPath(psUuid, vmUuid); + + CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); + cmsg.setPrimaryStorageUuid(psUuid); + cmsg.setVmUuid(vmUuid); + cmsg.setMetadataPath(metadataPath); + bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); + bus.send(cmsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.info(String.format("[MetadataExpunge] successfully deleted metadata for vm[uuid:%s] on ps[uuid:%s]", + vmUuid, psUuid)); + } else { + // best-effort: do not fail the expunge flow, MetadataStorageOrphanDetector will clean up later + logger.warn(String.format("[MetadataExpunge] failed to delete metadata for vm[uuid:%s] on ps[uuid:%s]: %s", + vmUuid, psUuid, reply.getError())); + } + trigger.next(); + } + }); + } +} 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..4c0c0964294 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,89 @@ public class VmGlobalConfig { @GlobalConfigValidation(validValues = {"None", "AuthenticAMD"}) @BindResourceConfig(value = {VmInstanceVO.class}) public static GlobalConfig VM_CPUID_VENDOR = new GlobalConfig(CATEGORY, "vm.cpuid.vendor"); + + + + @GlobalConfigValidation(validValues = {"true", "false"}) + public static GlobalConfig VM_METADATA = new GlobalConfig(CATEGORY, "vm.metadata"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_PS_MAX_CONCURRENT = new GlobalConfig(CATEGORY, "vm.metadata.ps.maxConcurrent"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_GLOBAL_MAX_CONCURRENT = new GlobalConfig(CATEGORY, "vm.metadata.global.maxConcurrent"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_GC_INITIAL_DELAY_SEC = new GlobalConfig(CATEGORY, "vm.metadata.gc.initialDelaySec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_MAX_RETRY = new GlobalConfig(CATEGORY, "vm.metadata.maxRetry"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DIRTY_POLL_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.dirty.pollIntervalSec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DIRTY_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.dirty.batchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_UPGRADE_REFRESH_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.upgrade.refreshDelaySec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_UPGRADE_REFRESH_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.upgrade.refreshBatchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_NODE_LEFT_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.nodeLeft.delaySec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_STALE_RECOVERY_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.staleRecovery.intervalSec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_STALE_RECOVERY_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.staleRecovery.batchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_STALE_RECOVERY_MAX_CYCLES = new GlobalConfig(CATEGORY, "vm.metadata.staleRecovery.maxCycles"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_PENDING_API_TIMEOUT = new GlobalConfig(CATEGORY, "vm.metadata.pendingApi.timeoutMinutes"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_RETRY_BASE_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.retry.baseDelaySeconds"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_RETRY_MAX_EXPONENT = new GlobalConfig(CATEGORY, "vm.metadata.retry.maxExponent"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_INIT_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.init.batchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_INIT_BATCH_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.init.batchDelaySec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_ORPHAN_CHECK_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.orphanCheck.intervalSec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_ZOMBIE_CLAIM_THRESHOLD = new GlobalConfig(CATEGORY, "vm.metadata.zombieClaim.thresholdMinutes"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_STALE_CLAIM_THRESHOLD = new GlobalConfig(CATEGORY, "vm.metadata.staleClaim.thresholdMinutes"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_TRIGGER_FLUSH_STALE = new GlobalConfig(CATEGORY, "vm.metadata.triggerFlush.staleMinutes"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DELETE_MAX_RETRY = new GlobalConfig(CATEGORY, "vm.metadata.delete.maxRetry"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_DELETE_BASE_DELAY = new GlobalConfig(CATEGORY, "vm.metadata.delete.baseDelaySec"); + + public static GlobalConfig VM_METADATA_LAST_REFRESH_VERSION = new GlobalConfig(CATEGORY, "vm.metadata.lastRefreshVersion"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_CONTENT_CHECK_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.contentCheck.intervalSec"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_CONTENT_CHECK_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.contentCheck.batchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_STORAGE_ORPHAN_CHECK_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.storageOrphanCheck.intervalSec"); } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java b/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java index 52aa2282c87..89b56e921b7 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java @@ -307,4 +307,8 @@ public String desensitizeTag(SystemTag systemTag, String tag) { } public static PatternedSystemTag VM_STATE_PAUSED_AFTER_MIGRATE = new PatternedSystemTag(("vmPausedAfterMigrate"), VmInstanceVO.class); + + public static String VM_METADATA_REGISTERING_MN_UUID_TOKEN = "registeringMnUuid"; + public static PatternedSystemTag VM_METADATA_REGISTERING_MN_UUID = new PatternedSystemTag( + String.format("vmMetadata::registeringMnUuid::{%s}", VM_METADATA_REGISTERING_MN_UUID_TOKEN), VmInstanceVO.class); } diff --git a/conf/db/upgrade/V5.0.0__schema.sql b/conf/db/upgrade/V5.0.0__schema.sql new file mode 100644 index 00000000000..2d1215595c5 --- /dev/null +++ b/conf/db/upgrade/V5.0.0__schema.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS `zstack`.`VmMetadataDirtyVO` ( + `vmInstanceUuid` VARCHAR(32) NOT NULL, + `managementNodeUuid` VARCHAR(32) DEFAULT NULL, + `dirtyVersion` BIGINT NOT NULL DEFAULT 1, + `lastClaimTime` TIMESTAMP NULL DEFAULT NULL, + `storageStructureChange` TINYINT(1) NOT NULL DEFAULT 0, + `retryCount` INT NOT NULL DEFAULT 0, + `nextRetryTime` TIMESTAMP NULL DEFAULT NULL, + `lastOpDate` timestamp on update CURRENT_TIMESTAMP, + `createDate` 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; + +CREATE TABLE IF NOT EXISTS `zstack`.`VmMetadataFingerprintVO` ( + `vmInstanceUuid` VARCHAR(32) NOT NULL, + `metadataSnapshot` LONGTEXT, + `lastFlushTime` TIMESTAMP NULL DEFAULT NULL, + `lastFlushFailed` TINYINT(1) NOT NULL DEFAULT 0, + `staleRecoveryCount` INT NOT NULL DEFAULT 0, + PRIMARY KEY (`vmInstanceUuid`), + CONSTRAINT `fkVmMetadataFingerprintVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/conf/globalConfig/vm.xml b/conf/globalConfig/vm.xml index 8563169b335..bc818743c35 100755 --- a/conf/globalConfig/vm.xml +++ b/conf/globalConfig/vm.xml @@ -317,4 +317,236 @@ 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 + + + + vm + vm.metadata.upgrade.refreshDelaySec + Delay in seconds before full refresh after upgrade, waiting for rolling upgrade to complete + java.lang.Long + 600 + + + + vm + vm.metadata.upgrade.refreshBatchSize + Upgrade full refresh SQL batch size + java.lang.Integer + 1000 + + + + vm + vm.metadata.nodeLeft.delaySec + Delay in seconds after nodeLeft before takeover, reduces zombie MN race condition + java.lang.Long + 5 + + + + vm + vm.metadata.staleRecovery.intervalSec + MetadataStaleRecoveryTask scan interval in seconds + java.lang.Long + 1800 + + + + vm + vm.metadata.staleRecovery.batchSize + MetadataStaleRecoveryTask rows per scan batch + java.lang.Integer + 100 + + + + vm + vm.metadata.staleRecovery.maxCycles + Max consecutive stale recovery cycles per VM before circuit-break + java.lang.Integer + 10 + + + + vm + vm.metadata.pendingApi.timeoutMinutes + Pending API timeout cleanup threshold in minutes + java.lang.Long + 45 + + + + vm + vm.metadata.retry.baseDelaySeconds + Exponential backoff base delay in seconds + java.lang.Integer + 10 + + + + vm + vm.metadata.retry.maxExponent + Exponential backoff max exponent + java.lang.Integer + 10 + + + + vm + vm.metadata.init.batchSize + Batch size per round when enabling metadata (false to true init) + java.lang.Integer + 200 + + + + vm + vm.metadata.init.batchDelaySec + Delay in seconds between init batches to prevent IO storm + java.lang.Long + 5 + + + + vm + vm.metadata.orphanCheck.intervalSec + Orphan metadata detection interval in seconds + java.lang.Long + 3600 + + + + vm + vm.metadata.zombieClaim.thresholdMinutes + Zombie claim threshold in minutes: claimed dirty rows older than this are released + java.lang.Long + 15 + + + + vm + vm.metadata.staleClaim.thresholdMinutes + Stale claim threshold in minutes for background recovery task + java.lang.Long + 30 + + + + vm + vm.metadata.triggerFlush.staleMinutes + Inline stale claim takeover threshold in minutes for triggerFlushForVm hot path + java.lang.Long + 10 + + + + vm + vm.metadata.delete.maxRetry + Max retry count for deleteMetadata in ExpungeVmInstanceFlow + java.lang.Integer + 3 + + + + vm + vm.metadata.delete.baseDelaySec + Base delay in seconds for deleteMetadata retry backoff + java.lang.Long + 30 + + + + vm + vm.metadata.lastRefreshVersion + Last completed upgrade refresh version, prevents duplicate triggers across MNs. Internal use only + java.lang.String + + + + + vm + vm.metadata.contentCheck.intervalSec + Metadata content drift detection interval in seconds (default 6h) + java.lang.Long + 21600 + + + + vm + vm.metadata.contentCheck.batchSize + Batch size for metadata content drift detection keyset pagination + java.lang.Integer + 50 + + + + vm + vm.metadata.storageOrphanCheck.intervalSec + Storage-level orphan metadata detection interval in seconds. Scans primary storages for metadata files whose VM no longer exists or has migrated away. + java.lang.Long + 3600 + diff --git a/conf/persistence.xml b/conf/persistence.xml index aa295bcb365..27d8646e46b 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -211,5 +211,7 @@ org.zstack.header.resourceattribute.entity.ResourceAttributeKeyResourceTypeVO org.zstack.header.resourceattribute.entity.ResourceAttributeConstraintVO org.zstack.softwarePackage.header.SoftwarePackageVO + org.zstack.header.vm.metadata.VmMetadataDirtyVO + org.zstack.header.vm.metadata.VmMetadataFingerprintVO diff --git a/conf/serviceConfig/primaryStorage.xml b/conf/serviceConfig/primaryStorage.xml index 337ce4eaac3..88597aa6ebb 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.APIScanVmInstanceMetadataFromPrimaryStorageMsg + + \ No newline at end of file diff --git a/conf/serviceConfig/vmInstance.xml b/conf/serviceConfig/vmInstance.xml index ab73fb6bea9..afe05d3184a 100755 --- a/conf/serviceConfig/vmInstance.xml +++ b/conf/serviceConfig/vmInstance.xml @@ -264,4 +264,16 @@ org.zstack.header.vm.APIDeleteTemplatedVmInstanceMsg + + org.zstack.header.vm.metadata.APICleanupVmInstanceMetadataMsg + + + org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataMsg + + + org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataMsg + + + org.zstack.header.vm.metadata.APIGetVmInstanceMetadataFromPrimaryStorageMsg + diff --git a/conf/springConfigXml/VmInstanceManager.xml b/conf/springConfigXml/VmInstanceManager.xml index 20e094378aa..fb60868aaeb 100755 --- a/conf/springConfigXml/VmInstanceManager.xml +++ b/conf/springConfigXml/VmInstanceManager.xml @@ -118,6 +118,7 @@ org.zstack.compute.vm.VmExpungeRootVolumeFlow org.zstack.compute.vm.VmExpungeMemoryVolumeFlow org.zstack.compute.vm.VmExpungeCacheVolumeFlow + org.zstack.compute.vm.VmExpungeMetadataFlow diff --git a/header/src/main/java/org/zstack/header/host/APIGetPhysicalMachineBlockDevicesMsg.java b/header/src/main/java/org/zstack/header/host/APIGetPhysicalMachineBlockDevicesMsg.java index 1de3303c7ac..4b45ed203f1 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetPhysicalMachineBlockDevicesMsg.java +++ b/header/src/main/java/org/zstack/header/host/APIGetPhysicalMachineBlockDevicesMsg.java @@ -13,7 +13,7 @@ method = HttpMethod.GET, responseClass = APIGetPhysicalMachineBlockDevicesReply.class ) - public class APIGetPhysicalMachineBlockDevicesMsg extends APISyncCallMessage { +public class APIGetPhysicalMachineBlockDevicesMsg extends APISyncCallMessage { @APIParam(maxLength = 255) private String username; @APIParam(required = false, maxLength = 255) diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..c4099be67cc --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,35 @@ +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/scan", + method = HttpMethod.GET, + responseClass = APIScanVmInstanceMetadataFromPrimaryStorageReply.class +) +public class APIScanVmInstanceMetadataFromPrimaryStorageMsg 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 APIScanVmInstanceMetadataFromPrimaryStorageMsg __example__() { + APIScanVmInstanceMetadataFromPrimaryStorageMsg msg = new APIScanVmInstanceMetadataFromPrimaryStorageMsg(); + msg.setUuid(uuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..ccb81445cf1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageReply + +doc { + title "扫描主存储上的云主机元数据" + + category "主存储" + + desc """扫描指定主存储上所有云主机元数据文件,返回元数据摘要列表""" + + rest { + request { + url "GET /v1/primary-storage/vm-instances/metadata/scan" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIScanVmInstanceMetadataFromPrimaryStorageMsg.class + + desc """""" + + params { + + column { + name "uuid" + enclosedIn "" + desc "主存储UUID" + location "query" + type "String" + optional false + since "5.0.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "query" + type "List" + optional true + since "5.0.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "query" + type "List" + optional true + since "5.0.0" + } + } + } + + response { + clz APIScanVmInstanceMetadataFromPrimaryStorageReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java new file mode 100644 index 00000000000..b2db4c43c82 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.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(fieldsTo = {"all"}) +public class APIScanVmInstanceMetadataFromPrimaryStorageReply extends APIReply { + private List vmInstanceMetadata = new ArrayList<>(); + + public List getVmInstanceMetadata() { + return vmInstanceMetadata; + } + + public void setVmInstanceMetadata(List vmInstanceMetadata) { + this.vmInstanceMetadata = vmInstanceMetadata; + } + + public static APIScanVmInstanceMetadataFromPrimaryStorageReply __example__() { + APIScanVmInstanceMetadataFromPrimaryStorageReply reply = new APIScanVmInstanceMetadataFromPrimaryStorageReply(); + return reply; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..7cfe306d3e8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.storage.primary.VmMetadataScanEntry +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "扫描主存储上的云主机元数据返回" + + ref { + name "vmInstanceMetadata" + path "org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageReply.vmInstanceMetadata" + desc "云主机元数据摘要列表" + type "List" + since "5.0.0" + clz VmMetadataScanEntry.class + } + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageReply.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java new file mode 100644 index 00000000000..c50b17223d0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java @@ -0,0 +1,43 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + +public class CleanupVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String vmUuid; + private String rootVolumeUuid; + private String metadataPath; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getRootVolumeUuid() { + return rootVolumeUuid; + } + + public void setRootVolumeUuid(String rootVolumeUuid) { + this.rootVolumeUuid = rootVolumeUuid; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java new file mode 100644 index 00000000000..05bba3ac430 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +public class CleanupVmInstanceMetadataOnPrimaryStorageReply extends MessageReply { +} 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..67ea49e5e18 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,44 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + + +public class GetVmInstanceMetadataFromPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String metadataPath; + private String rootVolumeUuid; + private String hostUuid; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + public String getRootVolumeUuid() { + return rootVolumeUuid; + } + + public void setRootVolumeUuid(String rootVolumeUuid) { + this.rootVolumeUuid = rootVolumeUuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } +} 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..c164a99792d --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/GetVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +public class GetVmInstanceMetadataFromPrimaryStorageReply 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/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/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/RebaseVolumeBackingFileOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java new file mode 100644 index 00000000000..f9dd68d2934 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageMsg.java @@ -0,0 +1,70 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + +import java.util.List; + +/** + * 请求目标主存储对指定文件做 backing file 前缀替换(prefix rebase)。 + *

+ * 各存储插件(LocalStorage / SharedBlock / NFS)自行选择 host、构造 agent command 并发送。 + */ +public class RebaseVolumeBackingFileOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + + /** + * volume + snapshot 的 installPath 列表(已做路径替换的逻辑路径)。 + * LocalStorage / NFS 下即绝对路径;SharedBlock 下为 sharedblock:// scheme 路径,由插件内部转绝对路径。 + */ + private List installPaths; + + /** 旧路径前缀 */ + private String oldPrefix; + + /** 新路径前缀 */ + private String newPrefix; + + /** 注册请求指定的 hostUuid(LocalStorage 需要,其他存储可忽略) */ + private String hostUuid; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public List getInstallPaths() { + return installPaths; + } + + public void setInstallPaths(List installPaths) { + this.installPaths = installPaths; + } + + public String getOldPrefix() { + return oldPrefix; + } + + public void setOldPrefix(String oldPrefix) { + this.oldPrefix = oldPrefix; + } + + public String getNewPrefix() { + return newPrefix; + } + + public void setNewPrefix(String newPrefix) { + this.newPrefix = newPrefix; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java new file mode 100644 index 00000000000..44043421f09 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/RebaseVolumeBackingFileOnPrimaryStorageReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +public class RebaseVolumeBackingFileOnPrimaryStorageReply extends MessageReply { + private int rebasedCount; + + public int getRebasedCount() { + return rebasedCount; + } + + public void setRebasedCount(int rebasedCount) { + this.rebasedCount = rebasedCount; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..052e77f7507 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,25 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + +public class ScanVmInstanceMetadataFromPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String metadataDir; + + @Override + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getMetadataDir() { + return metadataDir; + } + + public void setMetadataDir(String metadataDir) { + this.metadataDir = metadataDir; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java new file mode 100644 index 00000000000..655de95bc6c --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ScanVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,18 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.MessageReply; + +import java.util.ArrayList; +import java.util.List; + +public class ScanVmInstanceMetadataFromPrimaryStorageReply 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/VmMetadataScanEntry.java b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java new file mode 100644 index 00000000000..9859bc2aca7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntry.java @@ -0,0 +1,94 @@ +package org.zstack.header.storage.primary; + +public class VmMetadataScanEntry { + private String vmUuid; + private String vmName; + private String vmCategory; + private String architecture; + private String schemaVersion; + private String metadataPath; + private String hostUuid; + private long sizeBytes; + private long lastUpdateTime; + private boolean incomplete; + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getVmCategory() { + return vmCategory; + } + + public void setVmCategory(String vmCategory) { + this.vmCategory = vmCategory; + } + + public String getArchitecture() { + return architecture; + } + + public void setArchitecture(String architecture) { + this.architecture = architecture; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public long getSizeBytes() { + return sizeBytes; + } + + public void setSizeBytes(long sizeBytes) { + this.sizeBytes = sizeBytes; + } + + public long getLastUpdateTime() { + return lastUpdateTime; + } + + public void setLastUpdateTime(long lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } + + public boolean isIncomplete() { + return incomplete; + } + + public void setIncomplete(boolean incomplete) { + this.incomplete = incomplete; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java index 966a7d1030c..4a15afd4a6b 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIDeleteVolumeSnapshotMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.*; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.List; import java.util.concurrent.TimeUnit; @@ -44,6 +45,7 @@ responseClass = APIDeleteVolumeSnapshotEvent.class ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 6) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver") public class APIDeleteVolumeSnapshotMsg extends APIDeleteMessage implements DeleteVolumeSnapshotMessage { /** * @desc volume snapshot uuid diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java index 744f13038b6..28f7fae4106 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIRevertVolumeFromSnapshotMsg.java @@ -8,6 +8,7 @@ import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.volume.VolumeVO; import java.util.concurrent.TimeUnit; @@ -46,6 +47,7 @@ responseClass = APIRevertVolumeFromSnapshotEvent.class ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver") public class APIRevertVolumeFromSnapshotMsg extends APIMessage implements RevertVolumeSnapshotMessage, APIAuditor { /** * @desc volume snapshot uuid diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java index d5f00be9237..70a8169e96e 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIShrinkVolumeSnapshotMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.message.DefaultTimeout; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -20,6 +21,7 @@ isAction = true ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotBasedVmUuidFromApiResolver") public class APIShrinkVolumeSnapshotMsg extends APIMessage implements VolumeSnapshotMessage { @APIParam(resourceType = VolumeSnapshotVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java index 870a9ceacd4..cfb61ac8d35 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/APIUpdateVolumeSnapshotMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 6/14/2015. @@ -15,6 +16,7 @@ isAction = true, responseClass = APIUpdateVolumeSnapshotEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SnapshotBasedVmUuidFromApiResolver") public class APIUpdateVolumeSnapshotMsg extends APIMessage implements VolumeSnapshotMessage { @APIParam(resourceType = VolumeSnapshotVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java index 4afc5170734..59c532f138a 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java @@ -7,6 +7,7 @@ import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.snapshot.SnapshotBackendOperation; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -19,6 +20,7 @@ responseClass = APIDeleteVolumeSnapshotGroupEvent.class ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true) public class APIDeleteVolumeSnapshotGroupMsg extends APIDeleteMessage implements VolumeSnapshotGroupMessage { @APIParam(resourceType = VolumeSnapshotGroupVO.class, successIfResourceNotExisting = true) private String uuid; diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java index d042ee6229f..d6c9ad986e2 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIRevertVmFromSnapshotGroupMsg.java @@ -14,6 +14,7 @@ import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.snapshot.SnapshotBackendOperation; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -25,6 +26,7 @@ ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true) public class APIRevertVmFromSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage, APIAuditor { @APIParam(resourceType = VolumeSnapshotGroupVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java index daa39380ac1..fca12b2d074 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUngroupVolumeSnapshotGroupMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.snapshot.SnapshotBackendOperation; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by MaJin on 2019/7/9. @@ -14,6 +15,7 @@ method = HttpMethod.DELETE, responseClass = APIUngroupVolumeSnapshotGroupEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver") public class APIUngroupVolumeSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage { @APIParam(resourceType = VolumeSnapshotGroupVO.class, successIfResourceNotExisting = true) private String uuid; diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java index 0cee28cd6ea..34d52d63a14 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/group/APIUpdateVolumeSnapshotGroupMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.storage.snapshot.SnapshotBackendOperation; /** @@ -15,6 +16,7 @@ isAction = true, responseClass = APIUpdateVolumeSnapshotGroupEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "SnapshotGroupBasedVmUuidFromApiResolver") public class APIUpdateVolumeSnapshotGroupMsg extends APIMessage implements VolumeSnapshotGroupMessage { @APIParam(required = false) private String name; diff --git a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java index a02a6001cf1..09c06014229 100755 --- a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java +++ b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagMsg.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpMethod; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** */ @@ -11,6 +12,7 @@ responseClass = APICreateSystemTagEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APICreateSystemTagMsg extends APIAbstractCreateTagMsg { public static APICreateSystemTagMsg __example__() { diff --git a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java index 170076094d9..98196f68c20 100644 --- a/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java +++ b/header/src/main/java/org/zstack/header/tag/APICreateSystemTagsMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.vo.ResourceVO; import java.util.List; @@ -16,6 +17,7 @@ responseClass = APICreateSystemTagsEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APICreateSystemTagsMsg extends APIMessage { @APIParam private String resourceType; diff --git a/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java b/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java index b1159b20945..deef8615efa 100755 --- a/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java +++ b/header/src/main/java/org/zstack/header/tag/APIDeleteTagMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** */ @@ -12,6 +13,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteTagEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APIDeleteTagMsg extends APIDeleteMessage { @APIParam private String uuid; diff --git a/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java b/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java index 2962ce07061..0413eeaff95 100755 --- a/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java +++ b/header/src/main/java/org/zstack/header/tag/APIUpdateSystemTagMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 8/17/2015. @@ -14,6 +15,7 @@ isAction = true, method = HttpMethod.PUT ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APIUpdateSystemTagMsg extends APIMessage { @APIParam(resourceType = SystemTagVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java index c28f55a3c6c..2c132de7731 100755 --- a/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIAttachIsoToVmInstanceMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 10/17/2015. @@ -16,6 +17,7 @@ responseClass = APIAttachIsoToVmInstanceEvent.class, parameterName = "null" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIAttachIsoToVmInstanceMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java index 7bab9be06f9..3264c6de796 100755 --- a/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIAttachL3NetworkToVmMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.utils.network.NicIpAddressInfo; import java.util.List; @@ -45,6 +46,7 @@ method = HttpMethod.POST, responseClass = APIAttachL3NetworkToVmEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIAttachL3NetworkToVmMsg extends APIMessage implements VmInstanceMessage { /** * @desc vm uuid diff --git a/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java index 05b86a2bedf..d0b61bfed5b 100644 --- a/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIAttachVmNicToVmMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{vmInstanceUuid}/nices/{vmNicUuid}", @@ -11,6 +12,7 @@ responseClass = APIAttachVmNicToVmEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIAttachVmNicToVmMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmNicVO.class) diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java index aa0e68f9211..5ad4fb2c7b8 100755 --- a/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIChangeInstanceOfferingMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 7/16/2015. @@ -15,6 +16,7 @@ method = HttpMethod.PUT, responseClass = APIChangeInstanceOfferingEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIChangeInstanceOfferingMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java index 60ded8149d2..a1647122d8c 100644 --- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.List; import java.util.Map; @@ -16,6 +17,7 @@ method = HttpMethod.POST, responseClass = APIChangeVmNicNetworkEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver") public class APIChangeVmNicNetworkMsg extends APIMessage implements VmInstanceMessage{ @APIParam(resourceType = VmNicVO.class) private String vmNicUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java index 6c1594fed23..051dfcc785c 100644 --- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicStateMsg.java @@ -9,6 +9,7 @@ import org.zstack.header.other.APIMultiAuditor; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.ArrayList; import java.util.List; @@ -22,6 +23,7 @@ responseClass = APIChangeVmNicStateEvent.class, isAction = true ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver") public class APIChangeVmNicStateMsg extends APIMessage implements VmInstanceMessage, APIMultiAuditor { @APIParam(resourceType = VmNicVO.class) private String vmNicUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java index 1a128bfaf84..df22290f8dc 100644 --- a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java @@ -7,6 +7,7 @@ import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{templatedVmInstanceUuid}/convert-to-vmInstance", @@ -14,6 +15,7 @@ responseClass = APIConvertTemplatedVmInstanceToVmInstanceEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIConvertTemplatedVmInstanceToVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor { @APIParam(resourceType = TemplatedVmInstanceVO.class) private String templatedVmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java index 2b9191824b7..798b54c1b3e 100644 --- a/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIConvertVmInstanceToTemplatedVmInstanceMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{vmInstanceUuid}/convert-to-templatedVmInstance", @@ -13,6 +14,7 @@ responseClass = APIConvertVmInstanceToTemplatedVmInstanceEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIConvertVmInstanceToTemplatedVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java b/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java index 4e9cd9c8545..0c5c6698250 100644 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmNicMsg.java @@ -9,6 +9,7 @@ import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.RestRequest; import org.zstack.header.tag.TagResourceType; +import org.zstack.header.vm.metadata.MetadataImpact; @TagResourceType(VmNicVO.class) @@ -18,6 +19,7 @@ responseClass = APICreateVmNicEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APICreateVmNicMsg extends APICreateMessage implements APIAuditor { /** diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java index a2b73a69527..1ffc496f483 100644 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmBootModeMsg.java @@ -4,12 +4,14 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/bootmode", method = HttpMethod.DELETE, responseClass = APIDeleteVmBootModeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDeleteVmBootModeMsg extends APIDeleteMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java index e9d6152c7ef..4bd48ed4b37 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmConsolePasswordMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by root on 8/2/16. @@ -13,6 +14,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteVmConsolePasswordEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDeleteVmConsolePasswordMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java index 611c1c13da4..fa20a680192 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmHostnameMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 2/26/2016. @@ -13,6 +14,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteVmHostnameEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDeleteVmHostnameMsg extends APIDeleteMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java index 19fc5f5e8bc..950c6ff9bff 100644 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmNicMsg.java @@ -4,12 +4,14 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/nics/{uuid}", method = HttpMethod.DELETE, responseClass = APIDeleteVmNicEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "PreCaptureNicBasedVmUuidFromApiResolver") public class APIDeleteVmNicMsg extends APIDeleteMessage { @APIParam(resourceType = VmNicVO.class, successIfResourceNotExisting = true) diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java index 0372c7526ab..8e78f904961 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java @@ -3,6 +3,7 @@ import org.springframework.http.HttpMethod; import org.zstack.header.message.APIMessage; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by luchukun on 8/4/16. @@ -12,6 +13,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteVmSshKeyEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDeleteVmSshKeyMsg extends APIMessage implements VmInstanceMessage { private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java index 15d11f96647..29d2f2dc9f9 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDeleteVmStaticIpMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 2/26/2016. @@ -13,6 +14,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteVmStaticIpEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDeleteVmStaticIpMsg extends APIDeleteMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java index b93c5eb4361..b25ba80cf50 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDestroyVmInstanceMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.List; @@ -41,6 +42,7 @@ method = HttpMethod.DELETE, responseClass = APIDestroyVmInstanceEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver") public class APIDestroyVmInstanceMsg extends APIDeleteMessage implements VmInstanceMessage { /** * @desc vm uuid diff --git a/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java index 52d17b61fec..8fd524ff08b 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDetachIsoFromVmInstanceMsg.java @@ -7,6 +7,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 10/17/2015. @@ -16,6 +17,7 @@ method = HttpMethod.DELETE, responseClass = APIDetachIsoFromVmInstanceEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDetachIsoFromVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java index 0d2c7d2d38f..605b5bbf24c 100755 --- a/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIDetachL3NetworkFromVmMsg.java @@ -9,6 +9,7 @@ import org.zstack.header.other.APIMultiAuditor; import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.ArrayList; import java.util.List; @@ -21,6 +22,7 @@ method = HttpMethod.DELETE, responseClass = APIDetachL3NetworkFromVmEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIDetachL3NetworkFromVmMsg extends APIMessage implements VmInstanceMessage, APIMultiAuditor { @APIParam(resourceType = VmNicVO.class) private String vmNicUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java index 7b9ab119a4f..187813eb339 100755 --- a/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIExpungeVmInstanceMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.message.DefaultTimeout; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -18,6 +19,7 @@ method = HttpMethod.PUT ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver") public class APIExpungeVmInstanceMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java b/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java index e6ed3bbf44b..f7e361bc633 100755 --- a/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIMigrateVmMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.message.DefaultTimeout; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -44,6 +45,7 @@ ) @SkipVmTracer(replyClass = APIMigrateVmEvent.class) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 1) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver") public class APIMigrateVmMsg extends APIMessage implements VmInstanceMessage, MigrateVmMessage, CheckAttachedVolumesMessage { /** * @desc vm uuid diff --git a/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java index 7de84b5dccd..7c6c8e5fdf6 100755 --- a/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIRecoverVmInstanceMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 11/12/2015. @@ -14,6 +15,7 @@ method = HttpMethod.PUT, responseClass = APIRecoverVmInstanceEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIRecoverVmInstanceMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java index 53ad2c26f4f..5c37bb7b577 100755 --- a/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIReimageVmInstanceMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by miao on 11/3/16. @@ -15,6 +16,7 @@ responseClass = APIReimageVmInstanceEvent.class, category = "vmInstance" ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver") public class APIReimageVmInstanceMsg extends APIMessage implements VmInstanceMessage { public String getVmInstanceUuid() { return vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java index 0aa6e025c3c..ff9f0324892 100644 --- a/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootModeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/actions", @@ -11,6 +12,7 @@ isAction = true, responseClass = APISetVmBootModeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmBootModeMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java index fb3d980e807..10d01b3ce49 100755 --- a/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootOrderMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.List; @@ -18,6 +19,7 @@ method = HttpMethod.PUT, responseClass = APISetVmBootOrderEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmBootOrderMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java index a72619161e0..296bff71204 100644 --- a/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmBootVolumeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.volume.VolumeVO; /** @@ -16,6 +17,7 @@ method = HttpMethod.PUT, responseClass = APISetVmBootVolumeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmBootVolumeMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java index 1f25eead1db..68352ad2806 100644 --- a/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmClockTrackMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/actions", @@ -11,6 +12,7 @@ method = HttpMethod.PUT, responseClass = APISetVmClockTrackEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmClockTrackMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java index 0812289e799..18e45c98b18 100755 --- a/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmConsolePasswordMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.io.Serializable; @@ -18,6 +19,7 @@ method = HttpMethod.PUT, responseClass = APISetVmConsolePasswordEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmConsolePasswordMsg extends APIMessage implements VmInstanceMessage, Serializable { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java index 2b2e54afabc..e25665ad564 100755 --- a/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmHostnameMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 2/26/2016. @@ -14,6 +15,7 @@ isAction = true, responseClass = APISetVmHostnameEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmHostnameMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java index c5d7ebd8e72..3033445398a 100644 --- a/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmQxlMemoryMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/actions", @@ -11,6 +12,7 @@ method = HttpMethod.PUT, responseClass = APISetVmQxlMemoryEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmQxlMemoryMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java index 686697b009c..ed7a167176f 100644 --- a/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmSoundTypeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/actions", @@ -11,6 +12,7 @@ method = HttpMethod.PUT, responseClass = APISetVmSoundTypeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmSoundTypeMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java index 3b2d315ea06..d87244cd8c6 100755 --- a/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmSshKeyMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by luchukun on 8/4/16. @@ -15,6 +16,7 @@ method = HttpMethod.PUT, responseClass = APISetVmSshKeyEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmSshKeyMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java index 094f9d4a54f..b4d67a8770f 100755 --- a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 2/26/2016. @@ -15,6 +16,7 @@ method = HttpMethod.PUT, responseClass = APISetVmStaticIpEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmStaticIpMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java index 60e9343ff38..82856bebf31 100755 --- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 6/14/2015. @@ -15,6 +16,7 @@ isAction = true, responseClass = APIUpdateVmInstanceEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIUpdateVmInstanceMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java index de2fd27289c..508a5882666 100644 --- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmNicDriverMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * @ Author : yh.w @@ -15,6 +16,7 @@ isAction = true, responseClass = APIUpdateVmNicDriverEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "NicBasedVmUuidFromApiResolver") public class APIUpdateVmNicDriverMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java index a8c6f821450..a6127313a12 100644 --- a/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmPriorityMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; @RestRequest( path = "/vm-instances/{uuid}/actions", @@ -11,6 +12,7 @@ isAction = true, responseClass = APIUpdateVmPriorityEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "DefaultVmUuidFromApiResolver", updateOnFailure = false) public class APIUpdateVmPriorityMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmInstanceVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java index b71d8f3be45..1e5a9947ec9 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java @@ -30,7 +30,8 @@ public enum VmInstanceState { Error(null), NoState(VmInstanceStateEvent.noState), Unknown(VmInstanceStateEvent.unknown), - Crashed(VmInstanceStateEvent.crashed); + Crashed(VmInstanceStateEvent.crashed), + Registering(null); public static List intermediateStates = new ArrayList<>(); @@ -52,6 +53,7 @@ public enum VmInstanceState { offlineStates.add(Destroyed); offlineStates.add(VolumeMigrating); offlineStates.add(Crashed); + offlineStates.add(Registering); Created.transactions( new Transaction(VmInstanceStateEvent.starting, VmInstanceState.Starting), diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java index 72f0c8e9d5a..c020f6817ef 100644 --- a/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java +++ b/header/src/main/java/org/zstack/header/vm/cdrom/APICreateVmCdRomMsg.java @@ -10,6 +10,7 @@ import org.zstack.header.rest.RestRequest; import org.zstack.header.tag.TagResourceType; import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.vm.VmInstanceVO; /** @@ -22,6 +23,7 @@ responseClass = APICreateVmCdRomEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APICreateVmCdRomMsg extends APICreateMessage implements APIAuditor, VmInstanceMessage { @APIParam(maxLength = 255) private String name; diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java index f5f9b0afa76..763fc8f2206 100644 --- a/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java +++ b/header/src/main/java/org/zstack/header/vm/cdrom/APISetVmInstanceDefaultCdRomMsg.java @@ -8,6 +8,7 @@ import org.zstack.header.rest.RestRequest; import org.zstack.header.vm.VmInstanceMessage; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Create by lining at 2018/12/29 @@ -18,6 +19,7 @@ isAction = true, responseClass = APISetVmInstanceDefaultCdRomEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APISetVmInstanceDefaultCdRomMsg extends APIMessage implements VmInstanceMessage, APIAuditor { @APIParam(resourceType = VmCdRomVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java b/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java index 927426e5e9a..8dbc6157c8b 100644 --- a/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java +++ b/header/src/main/java/org/zstack/header/vm/cdrom/APIUpdateVmCdRomMsg.java @@ -6,6 +6,7 @@ import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Create by lining at 2018/12/30 @@ -16,6 +17,7 @@ isAction = true, responseClass = APIUpdateVmCdRomEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "DefaultVmUuidFromApiResolver") public class APIUpdateVmCdRomMsg extends APIMessage implements VmInstanceMessage { @APIParam(resourceType = VmCdRomVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java new file mode 100644 index 00000000000..39313f598cb --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEvent.java @@ -0,0 +1,53 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +import java.util.List; + +@RestResponse(fieldsTo = {"all"}) +public class APICleanupVmInstanceMetadataEvent extends APIEvent { + private Integer totalCleaned; + private Integer totalFailed; + private List failedVmUuids; + + public APICleanupVmInstanceMetadataEvent() { + super(null); + } + + public APICleanupVmInstanceMetadataEvent(String apiId) { + super(apiId); + } + + public Integer getTotalCleaned() { + return totalCleaned; + } + + public void setTotalCleaned(Integer totalCleaned) { + this.totalCleaned = totalCleaned; + } + + public Integer getTotalFailed() { + return totalFailed; + } + + public void setTotalFailed(Integer totalFailed) { + this.totalFailed = totalFailed; + } + + public List getFailedVmUuids() { + return failedVmUuids; + } + + public void setFailedVmUuids(List failedVmUuids) { + this.failedVmUuids = failedVmUuids; + } + + public static APICleanupVmInstanceMetadataEvent __example__() { + APICleanupVmInstanceMetadataEvent evt = new APICleanupVmInstanceMetadataEvent(); + evt.totalCleaned = 5; + evt.totalFailed = 0; + evt.failedVmUuids = java.util.Collections.emptyList(); + return evt; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..3684e27594a --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,42 @@ +package org.zstack.header.vm.metadata + +import java.lang.Integer +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "清理云主机元数据返回" + + field { + name "totalCleaned" + desc "成功清理的元数据数量" + type "Integer" + since "5.0.0" + } + field { + name "totalFailed" + desc "清理失败的元数据数量" + type "Integer" + since "5.0.0" + } + field { + name "failedVmUuids" + desc "清理失败的云主机UUID列表" + type "List" + since "5.0.0" + } + field { + name "success" + desc "" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.vm.metadata.APICleanupVmInstanceMetadataEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..5823f66b78f --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsg.java @@ -0,0 +1,34 @@ +package org.zstack.header.vm.metadata; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.VmInstanceVO; + +import java.util.List; + +@RestRequest( + path = "/vm-instances/metadata/cleanup", + method = HttpMethod.PUT, + responseClass = APICleanupVmInstanceMetadataEvent.class, + isAction = true +) +public class APICleanupVmInstanceMetadataMsg extends APIMessage { + @APIParam(resourceType = VmInstanceVO.class, nonempty = true) + private List vmUuids; + + public List getVmUuids() { + return vmUuids; + } + + public void setVmUuids(List vmUuids) { + this.vmUuids = vmUuids; + } + + public static APICleanupVmInstanceMetadataMsg __example__() { + APICleanupVmInstanceMetadataMsg msg = new APICleanupVmInstanceMetadataMsg(); + msg.vmUuids = java.util.Arrays.asList(uuid(), uuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..0c29f0d6106 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.vm.metadata.APICleanupVmInstanceMetadataEvent + +doc { + title "清理云主机元数据" + + category "云主机" + + desc """清理指定云主机在主存储上的元数据文件""" + + rest { + request { + url "PUT /v1/vm-instances/metadata/cleanup" + + header (Authorization: 'OAuth the-session-uuid') + + clz APICleanupVmInstanceMetadataMsg.class + + desc """""" + + params { + + column { + name "vmUuids" + enclosedIn "cleanupVmInstanceMetadata" + desc "需要清理元数据的云主机UUID列表" + location "body" + type "List" + optional false + since "5.0.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.0.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.0.0" + } + } + } + + response { + clz APICleanupVmInstanceMetadataEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..6aacc147e13 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,37 @@ +package org.zstack.header.vm.metadata; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.APISyncCallMessage; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.VmInstanceVO; + +@RestRequest( + path = "/primary-storage/vm-instances/metadata", + method = HttpMethod.GET, + responseClass = APIGetVmInstanceMetadataFromPrimaryStorageReply.class +) +public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APISyncCallMessage implements VmInstanceMessage { + @APIParam(resourceType = VmInstanceVO.class) + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getVmInstanceUuid() { + return uuid; + } + + public static APIGetVmInstanceMetadataFromPrimaryStorageMsg __example__() { + APIGetVmInstanceMetadataFromPrimaryStorageMsg msg = new APIGetVmInstanceMetadataFromPrimaryStorageMsg(); + msg.setUuid(uuid(VmInstanceVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..b8f87cb3ee1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.vm.metadata.APIGetVmInstanceMetadataFromPrimaryStorageReply + +doc { + title "获取云主机元数据" + + category "主存储" + + desc """从主存储获取指定云主机的元数据内容""" + + rest { + request { + url "GET /v1/primary-storage/vm-instances/metadata" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIGetVmInstanceMetadataFromPrimaryStorageMsg.class + + desc """""" + + params { + + column { + name "uuid" + enclosedIn "" + desc "云主机UUID" + location "query" + type "String" + optional false + since "5.0.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "query" + type "List" + optional true + since "5.0.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "query" + type "List" + optional true + since "5.0.0" + } + } + } + + response { + clz APIGetVmInstanceMetadataFromPrimaryStorageReply.class + } + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java new file mode 100644 index 00000000000..b87e09d2dd4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,23 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.APIReply; +import org.zstack.header.rest.RestResponse; + + +@RestResponse(fieldsTo = {"all"}) +public class APIGetVmInstanceMetadataFromPrimaryStorageReply extends APIReply { + private String metadata; + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public static APIGetVmInstanceMetadataFromPrimaryStorageReply __example__() { + APIGetVmInstanceMetadataFromPrimaryStorageReply reply = new APIGetVmInstanceMetadataFromPrimaryStorageReply(); + return reply; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..1da3de4ee9e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIGetVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy @@ -0,0 +1,29 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "获取云主机元数据返回" + + field { + name "metadata" + desc "云主机元数据内容" + type "String" + since "5.0.0" + } + field { + name "success" + desc "" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.vm.metadata.APIGetVmInstanceMetadataFromPrimaryStorageReply.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java new file mode 100644 index 00000000000..e0e62c75eea --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEvent.java @@ -0,0 +1,46 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.vm.VmInstanceInventory; + +import java.util.List; + +@RestResponse(fieldsTo = {"all"}) +public class APIRegisterVmInstanceFromMetadataEvent extends APIEvent { + private VmInstanceInventory inventory; + private List warnings; + + public APIRegisterVmInstanceFromMetadataEvent() { + super(null); + } + + public APIRegisterVmInstanceFromMetadataEvent(String apiId) { + super(apiId); + } + + public VmInstanceInventory getInventory() { + return inventory; + } + + public void setInventory(VmInstanceInventory inventory) { + this.inventory = inventory; + } + + public List getWarnings() { + return warnings; + } + + public void setWarnings(List warnings) { + this.warnings = warnings; + } + + public static APIRegisterVmInstanceFromMetadataEvent __example__() { + APIRegisterVmInstanceFromMetadataEvent evt = new APIRegisterVmInstanceFromMetadataEvent(); + VmInstanceInventory vm = new VmInstanceInventory(); + vm.setUuid(uuid()); + vm.setName("recovered-vm"); + evt.setInventory(vm); + return evt; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..fc9a8f43001 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,38 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.vm.VmInstanceInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "从元数据注册云主机返回" + + ref { + name "inventory" + path "org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent.inventory" + desc "云主机详情" + type "VmInstanceInventory" + since "5.0.0" + clz VmInstanceInventory.class + } + field { + name "warnings" + desc "注册过程中的警告信息列表" + type "List" + since "5.0.0" + } + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java new file mode 100644 index 00000000000..544ee0c2c79 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsg.java @@ -0,0 +1,102 @@ +package org.zstack.header.vm.metadata; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.DefaultTimeout; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.zone.ZoneVO; +import org.zstack.header.cluster.ClusterVO; +import org.zstack.header.host.HostVO; +import org.zstack.header.tag.TagResourceType; + +import java.util.concurrent.TimeUnit; + +@TagResourceType(VmInstanceVO.class) +@RestRequest( + path = "/vm-instances/metadata/register", + method = HttpMethod.POST, + responseClass = APIRegisterVmInstanceFromMetadataEvent.class, + parameterName = "params" +) +@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3) +public class APIRegisterVmInstanceFromMetadataMsg extends APIMessage { + @APIParam(nonempty = true, maxLength = 2048) + private String metadataPath; + + @APIParam(resourceType = PrimaryStorageVO.class) + private String primaryStorageUuid; + + @APIParam(resourceType = ZoneVO.class) + private String zoneUuid; + + @APIParam(resourceType = ClusterVO.class) + private String clusterUuid; + + @APIParam(resourceType = HostVO.class) + private String hostUuid; + + @APIParam(required = false, maxLength = 255) + private String name; + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public void setZoneUuid(String zoneUuid) { + this.zoneUuid = zoneUuid; + } + + 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 getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static APIRegisterVmInstanceFromMetadataMsg __example__() { + APIRegisterVmInstanceFromMetadataMsg msg = new APIRegisterVmInstanceFromMetadataMsg(); + msg.metadataPath = "/vm-metadata/vm-uuid/metadata.json"; + msg.primaryStorageUuid = uuid(PrimaryStorageVO.class); + msg.zoneUuid = uuid(ZoneVO.class); + msg.clusterUuid = uuid(ClusterVO.class); + msg.hostUuid = uuid(HostVO.class); + msg.name = "my-restored-vm"; + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..b14a849ce15 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,112 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.vm.metadata.APIRegisterVmInstanceFromMetadataEvent + +doc { + title "从元数据注册云主机(RegisterVmInstanceFromMetadata)" + + category "云主机" + + desc """根据主存储上的元数据文件注册(恢复)一台云主机""" + + rest { + request { + url "POST /v1/vm-instances/metadata/register" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIRegisterVmInstanceFromMetadataMsg.class + + desc """""" + + params { + + column { + name "metadataPath" + enclosedIn "params" + desc "元数据文件在主存储上的路径" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "primaryStorageUuid" + enclosedIn "params" + desc "目标主存储UUID" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "zoneUuid" + enclosedIn "params" + desc "区域UUID" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "clusterUuid" + enclosedIn "params" + desc "集群UUID" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "hostUuid" + enclosedIn "params" + desc "物理机UUID" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "name" + enclosedIn "params" + desc "云主机名称" + location "body" + type "String" + optional true + since "5.0.0" + } + column { + name "tagUuids" + enclosedIn "params" + desc "标签UUID列表" + location "body" + type "List" + optional true + since "5.0.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.0.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.0.0" + } + } + } + + response { + clz APIRegisterVmInstanceFromMetadataEvent.class + } + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java new file mode 100644 index 00000000000..897f3a0ef4e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEvent.java @@ -0,0 +1,19 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse(fieldsTo = {"all"}) +public class APIUpdateVmInstanceMetadataEvent extends APIEvent { + public APIUpdateVmInstanceMetadataEvent() { + super(null); + } + + public APIUpdateVmInstanceMetadataEvent(String apiId) { + super(apiId); + } + + public static APIUpdateVmInstanceMetadataEvent __example__() { + return new APIUpdateVmInstanceMetadataEvent(); + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..e6d90be232e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,23 @@ +package org.zstack.header.vm.metadata + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "更新云主机元数据返回" + + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.vm.metadata.APIUpdateVmInstanceMetadataEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..46e529b4fbd --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsg.java @@ -0,0 +1,35 @@ +package org.zstack.header.vm.metadata; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.VmInstanceVO; + +import java.util.Collections; +import java.util.List; + +@RestRequest( + path = "/vm-instances/metadata/actions", + method = HttpMethod.PUT, + responseClass = APIUpdateVmInstanceMetadataEvent.class, + isAction = true +) +public class APIUpdateVmInstanceMetadataMsg extends APIMessage { + @APIParam(resourceType = VmInstanceVO.class, nonempty = true) + private List vmUuids; + + public List getVmUuids() { + return vmUuids; + } + + public void setVmUuids(List vmUuids) { + this.vmUuids = vmUuids; + } + + public static APIUpdateVmInstanceMetadataMsg __example__() { + APIUpdateVmInstanceMetadataMsg msg = new APIUpdateVmInstanceMetadataMsg(); + msg.vmUuids = Collections.singletonList(uuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..4ac5e855638 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,56 @@ +package org.zstack.header.vm.metadata + +doc { + title "更新云主机元数据" + + category "云主机" + + desc """立即触发指定云主机的元数据更新""" + + rest { + request { + url "PUT /v1/vm-instances/metadata/actions" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIUpdateVmInstanceMetadataMsg.class + + desc """""" + + params { + + column { + name "vmUuids" + enclosedIn "updateVmMetadata" + desc "需要更新元数据的云主机UUID列表" + location "body" + type "List" + optional false + since "5.0.0" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "body" + type "List" + optional true + since "5.0.0" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "body" + type "List" + optional true + since "5.0.0" + } + } + } + + response { + clz APIUpdateVmInstanceMetadataEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java b/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java new file mode 100644 index 00000000000..8cc152d01bd --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java @@ -0,0 +1,28 @@ +package org.zstack.header.vm.metadata; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetadataImpact { + Impact value(); + + /** + * Spring bean name of the {@link VmUuidFromApiResolver} that extracts vmUuid(s) + * from this API message. Must be specified for {@link Impact#CONFIG} and + * {@link Impact#STORAGE}; ignored for {@link Impact#NONE}. + * + *

The bean must be registered in Spring XML. Interceptor looks it up at + * startup via {@code ComponentLoader.getComponentByBeanName()}.

+ */ + String resolver() default ""; + + boolean updateOnFailure() default false; + + enum Impact { + NONE, + CONFIG, + STORAGE + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java b/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java new file mode 100644 index 00000000000..06b63852add --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java @@ -0,0 +1,8 @@ +package org.zstack.header.vm.metadata; + +public class ResourceMetadata { + public String resourceUuid; + public String vo; + public String systemTags; + public String resourceConfigs; +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..bf3956f41fc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataMsg.java @@ -0,0 +1,26 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.VmInstanceMessage; + +public class UpdateVmInstanceMetadataMsg extends NeedReplyMessage implements VmInstanceMessage { + private String vmInstanceUuid; + 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/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java new file mode 100644 index 00000000000..ba12e4b918e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageMsg.java @@ -0,0 +1,98 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.storage.primary.PrimaryStorageMessage; + +public class UpdateVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String vmInstanceUuid; + private String rootVolumeUuid; + private String vmInstanceName; + private String vmCategory; + private String architecture; + private String metadata; + private String schemaVersion; + private boolean storageStructureChange; + private String metadataPath; + + @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 getVmInstanceName() { + return vmInstanceName; + } + + public void setVmInstanceName(String vmInstanceName) { + this.vmInstanceName = vmInstanceName; + } + + public String getVmCategory() { + return vmCategory; + } + + public void setVmCategory(String vmCategory) { + this.vmCategory = vmCategory; + } + + public String getArchitecture() { + return architecture; + } + + public void setArchitecture(String architecture) { + this.architecture = architecture; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public boolean isStorageStructureChange() { + return storageStructureChange; + } + + public void setStorageStructureChange(boolean storageStructureChange) { + this.storageStructureChange = storageStructureChange; + } + + public String getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java new file mode 100644 index 00000000000..d8323171909 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataOnPrimaryStorageReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.MessageReply; + +public class UpdateVmInstanceMetadataOnPrimaryStorageReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java new file mode 100644 index 00000000000..98296a42b3c --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/UpdateVmInstanceMetadataReply.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.MessageReply; + +public class UpdateVmInstanceMetadataReply 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/vm/metadata/VmInstanceMetadataConstants.java b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java new file mode 100644 index 00000000000..3460cbfdc4f --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java @@ -0,0 +1,10 @@ +package org.zstack.header.vm.metadata; + +public class VmInstanceMetadataConstants { + private VmInstanceMetadataConstants() { + } + + public static final String SBLK_METADATA_LV_SUFFIX = "_vmmeta"; + public static final String FILE_METADATA_SUFFIX = ".vmmeta"; + public static final String METADATA_DIR_NAME = "vm-metadata"; +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java new file mode 100644 index 00000000000..b002f4a06d2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.metadata; + +import java.util.List; + +public class VmInstanceMetadataDTO { + public String schemaVersion; + public VmMetadataCategory vmCategory; + public String cacheVmInstanceUuid; + public ResourceMetadata vm; + public List volumes; + public List nics; + public List snapshots; + public List snapshotGroups; + public List snapshotGroupRefs; +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java new file mode 100644 index 00000000000..e793a1dfd74 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataCategory.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm.metadata; + +public enum VmMetadataCategory { + REGULAR, + TEMPLATE, + TEMPLATE_CACHE +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java new file mode 100644 index 00000000000..822901e63da --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataConstants.java @@ -0,0 +1,25 @@ +package org.zstack.header.vm.metadata; + +public final class VmMetadataConstants { + private VmMetadataConstants() { + // utility class + } + + public static final long SBLK_HEADER_SIZE = 4096L; + + public static final long SBLK_SLOT_HEADER_SIZE = 36L; + + public static final long SBLK_MAX_LV_SIZE = 64L * 1024 * 1024; + + public static long slotCapacity(long lvSize) { + return ((lvSize - SBLK_HEADER_SIZE) / 2 / 4096) * 4096; + } + + public static final long SBLK_MAX_SLOT_CAPACITY = slotCapacity(SBLK_MAX_LV_SIZE); + + public static final long SBLK_MAX_PAYLOAD_SIZE = SBLK_MAX_SLOT_CAPACITY - SBLK_SLOT_HEADER_SIZE; + + public static final long PAYLOAD_WARN_THRESHOLD = 8L * 1024 * 1024; + + public static final long PAYLOAD_REJECT_THRESHOLD = 30L * 1024 * 1024; +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java new file mode 100644 index 00000000000..ff17ffcf7f4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyService.java @@ -0,0 +1,29 @@ +package org.zstack.header.vm.metadata; + +/** + * Service interface for marking VM metadata as dirty. + *

+ * Implementations live in the premium module (VmMetadataDirtyMarker). + * The zstack core module uses this interface via {@code @Autowired(required = false)} + * so that the feature degrades gracefully when the premium module is not loaded. + */ +public interface VmMetadataDirtyService { + /** + * Mark the VM's metadata as dirty so that it will be flushed + * to primary storage on the next poll cycle. + * + * @param vmInstanceUuid the VM whose metadata changed + * @return true if the mark was actually written (feature enabled), false otherwise + */ + boolean markDirty(String vmInstanceUuid); + + /** + * Mark the VM's metadata as dirty, optionally flagging a storage-structure change + * (e.g. volume attach/detach, snapshot, migration). + * + * @param vmInstanceUuid the VM whose metadata changed + * @param storageStructureChange true if the change affects storage topology + * @return true if the mark was actually written (feature enabled), false otherwise + */ + boolean markDirty(String vmInstanceUuid, boolean storageStructureChange); +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java new file mode 100644 index 00000000000..f313699b82d --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java @@ -0,0 +1,121 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.managementnode.ManagementNodeVO; +import org.zstack.header.vm.VmInstanceEO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ForeignKey.ReferenceOption; + +import javax.persistence.*; +import java.sql.Timestamp; + +@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; + + @Column + private long dirtyVersion; + + @Column + private Timestamp lastClaimTime; + + @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 Timestamp getLastClaimTime() { + return lastClaimTime; + } + + public void setLastClaimTime(Timestamp lastClaimTime) { + this.lastClaimTime = lastClaimTime; + } + + 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/metadata/VmMetadataDirtyVO_.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java new file mode 100644 index 00000000000..059ff6ad20f --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO_.java @@ -0,0 +1,18 @@ +package org.zstack.header.vm.metadata; + +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 lastClaimTime; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java new file mode 100644 index 00000000000..f06ad8362dd --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataErrors.java @@ -0,0 +1,28 @@ +package org.zstack.header.vm.metadata; + +public enum VmMetadataErrors { + METADATA_INVALID_FORMAT(1300), + METADATA_SCHEMA_VERSION_MISMATCH(1301), + METADATA_UUID_CONFLICT(1302), + METADATA_STORAGE_NOT_SUPPORTED(1303), + METADATA_CROSS_STORAGE_FORBIDDEN(1304), + METADATA_INSTALL_PATH_NOT_FOUND(1305), + METADATA_CACHE_VM_NOT_REGISTERABLE(1306), + METADATA_VM_REGISTERING(1307), + METADATA_READ_CORRUPTED(1308), + METADATA_PAYLOAD_TOO_LARGE(1309), + METADATA_PS_UNREACHABLE(1310), + METADATA_FEATURE_DISABLED(1311), + ; + + private String code; + + private VmMetadataErrors(int id) { + code = String.format("VM_METADATA.%s", id); + } + + @Override + public String toString() { + return code; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java new file mode 100644 index 00000000000..ff2853e2971 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFingerprintVO.java @@ -0,0 +1,88 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.vm.VmInstanceEO; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ForeignKey.ReferenceOption; + +import javax.persistence.*; +import java.sql.Timestamp; + +/** + * 元数据指纹:记录每台 VM 上次成功刷写元数据时的完整快照。 + * + *

设计要点

+ *
    + *
  • metadataSnapshot:完整元数据 JSON(确定性序列化), + * 由 {@code MetadataContentDriftDetector} 每 6 小时低频对比, + * 发现漂移则触发 markDirty 重新刷写。
  • + *
  • lastFlushFailed:Poller 重试耗尽时置 true(C-SR-05), + * 仅由 {@code MetadataStaleRecoveryTask} 重置为 false(C-02B-8)。
  • + *
  • staleRecoveryCount:熔断计数器,{@code MetadataStaleRecoveryTask} 每次 + * 重入队递增,达到上限(默认 10 × 5 小时)后停止自动恢复。 + * 管理员可通过 {@code APIUpdateVmMetadataMsg} 手动重置为 0。
  • + *
  • vmInstanceUuid 作 PK:一台 VM 最多一行, + * FK CASCADE 保证 VM 物理删除时自动清理。
  • + *
+ */ +@Entity +@Table(name = "VmMetadataFingerprintVO") +public class VmMetadataFingerprintVO { + + @Id + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE) + private String vmInstanceUuid; + + @Column + private Timestamp lastFlushTime; + + @Column + private boolean lastFlushFailed; + + @Column + private int staleRecoveryCount; + + @Column + @Lob + private String metadataSnapshot; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public Timestamp getLastFlushTime() { + return lastFlushTime; + } + + public void setLastFlushTime(Timestamp lastFlushTime) { + this.lastFlushTime = lastFlushTime; + } + + public boolean isLastFlushFailed() { + return lastFlushFailed; + } + + public void setLastFlushFailed(boolean lastFlushFailed) { + this.lastFlushFailed = lastFlushFailed; + } + + public int getStaleRecoveryCount() { + return staleRecoveryCount; + } + + public void setStaleRecoveryCount(int staleRecoveryCount) { + this.staleRecoveryCount = staleRecoveryCount; + } + + public String getMetadataSnapshot() { + return metadataSnapshot; + } + + public void setMetadataSnapshot(String metadataSnapshot) { + this.metadataSnapshot = metadataSnapshot; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java new file mode 100644 index 00000000000..d1debc03930 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm.metadata; + +public interface VmMetadataPathBuildExtensionPoint { + String getPrimaryStorageType(); + String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid); + String buildMetadataDir(String primaryStorageUuid); +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java new file mode 100644 index 00000000000..38465deb9ca --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java @@ -0,0 +1,44 @@ +package org.zstack.header.vm.metadata; + +import java.util.List; +import java.util.Map; + +public interface VmMetadataPathReplacementExtensionPoint { + String getPrimaryStorageType(); + PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths); + class PathReplacementResult { + /** old path → new path 完整映射 */ + private Map pathMap; + /** rebase 前缀替换用的旧路径前缀 */ + private String oldPrefix; + /** rebase 前缀替换用的新路径前缀 */ + private String newPrefix; + + public PathReplacementResult() { + } + + public Map getPathMap() { + return pathMap; + } + + public void setPathMap(Map pathMap) { + this.pathMap = pathMap; + } + + public String getOldPrefix() { + return oldPrefix; + } + + public void setOldPrefix(String oldPrefix) { + this.oldPrefix = oldPrefix; + } + + public String getNewPrefix() { + return newPrefix; + } + + public void setNewPrefix(String newPrefix) { + this.newPrefix = newPrefix; + } + } +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java new file mode 100644 index 00000000000..bda1d0a09ed --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.vm.metadata; + +import java.sql.Timestamp; + +public interface VmMetadataResourcePersistExtensionPoint { + String getPrimaryStorageType(); + void afterVolumePersist(String primaryStorageUuid, String resourceUuid, + String resourceType, String hostUuid, long size, Timestamp now); + void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid, + String resourceType, String hostUuid, long size, Timestamp now); +} diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java b/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java new file mode 100644 index 00000000000..5db019052cd --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java @@ -0,0 +1,11 @@ +package org.zstack.header.vm.metadata; + +import org.zstack.header.message.APIMessage; + +import java.util.List; + +public interface VmUuidFromApiResolver { + boolean supports(APIMessage msg); + + List resolveVmUuids(APIMessage msg); +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java b/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java new file mode 100644 index 00000000000..bc08c457b17 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java @@ -0,0 +1,8 @@ +package org.zstack.header.vm.metadata; + +import java.util.List; + +public class VolumeResourceMetadata extends ResourceMetadata { + public List snapshotReferences; + public List snapshotReferenceTrees; +} diff --git a/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java b/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java index 6ae817723f4..46471a0bb8a 100755 --- a/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIAttachDataVolumeToVmMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.metadata.MetadataImpact; /** * @api api event for message :ref:`APIAttachVolumeToVmEvent` @@ -39,6 +40,7 @@ parameterName = "params", responseClass = APIAttachDataVolumeToVmEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIAttachDataVolumeToVmMsg extends APIMessage implements VolumeMessage { /** * @desc vm uuid. see :ref:`VmInstanceInventory` diff --git a/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java b/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java index 30478454c67..cd69d10122b 100755 --- a/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIChangeVolumeStateMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * @api change data volume state @@ -38,6 +39,7 @@ method = HttpMethod.PUT, responseClass = APIChangeVolumeStateEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIChangeVolumeStateMsg extends APIMessage implements VolumeMessage { /** * @desc data volume uuid diff --git a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java index 9b497a73fe4..39b8bd0afbe 100644 --- a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotGroupMsg.java @@ -13,6 +13,7 @@ import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; import org.zstack.header.vm.VmInstanceInventory; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.ArrayList; import java.util.List; @@ -27,6 +28,7 @@ responseClass = APICreateVolumeSnapshotGroupEvent.class, parameterName = "params" ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "SnapshotGroupBasedVmUuidFromApiResolver", updateOnFailure = true) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3) public class APICreateVolumeSnapshotGroupMsg extends APICreateMessage implements VolumeMessage, CreateVolumeSnapshotGroupMessage, APIMultiAuditor { /** diff --git a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java index 35f57d205f6..65dacf0c367 100755 --- a/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APICreateVolumeSnapshotMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -45,6 +46,7 @@ parameterName = "params" ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 3) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver", updateOnFailure = true) public class APICreateVolumeSnapshotMsg extends APICreateMessage implements VolumeMessage, APIAuditor { /** * @desc volume uuid. See :ref:`VolumeInventory` diff --git a/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java index fdbb1be9847..927149674a0 100755 --- a/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIDeleteDataVolumeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.List; @@ -42,6 +43,7 @@ method = HttpMethod.DELETE, responseClass = APIDeleteDataVolumeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver") public class APIDeleteDataVolumeMsg extends APIDeleteMessage implements VolumeMessage { /** * @desc data volume uuid diff --git a/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java b/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java index 5164ffca076..5c3a7d91864 100755 --- a/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIDetachDataVolumeFromVmMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.rest.RestRequest; /** @@ -36,6 +37,7 @@ method = HttpMethod.DELETE, responseClass = APIDetachDataVolumeFromVmEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver") public class APIDetachDataVolumeFromVmMsg extends APIMessage implements VolumeMessage { /** * @desc data volume uuid. See :ref:`VolumeInventory` diff --git a/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java index 272036c9378..04e318b1fbd 100755 --- a/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIExpungeDataVolumeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 11/16/2015. @@ -14,6 +15,7 @@ method = HttpMethod.PUT, responseClass = APIExpungeDataVolumeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "PreCaptureVolumeBasedVmUuidFromApiResolver") public class APIExpungeDataVolumeMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class, successIfResourceNotExisting = true) private String uuid; diff --git a/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java index daeb56b44eb..b969660f42f 100644 --- a/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIFlattenVolumeMsg.java @@ -7,6 +7,7 @@ import org.zstack.header.message.DefaultTimeout; import org.zstack.header.other.APIAuditor; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import java.util.concurrent.TimeUnit; @@ -17,6 +18,7 @@ method = HttpMethod.PUT, responseClass = APIFlattenVolumeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIFlattenVolumeMsg extends APIMessage implements VolumeMessage, APIAuditor { @APIParam(resourceType = VolumeVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java index eec77334a37..df55f75651a 100755 --- a/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIRecoverDataVolumeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 11/12/2015. @@ -14,6 +15,7 @@ method = HttpMethod.PUT, responseClass = APIRecoverDataVolumeEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIRecoverDataVolumeMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java b/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java index d4780c73be0..fc311d4a0d1 100755 --- a/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APISyncVolumeSizeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by xing5 on 2016/4/24. @@ -14,6 +15,7 @@ responseClass = APISyncVolumeSizeEvent.class, isAction = true ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver") public class APISyncVolumeSizeMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java b/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java index 0d27cfaa18a..81c30b9ec79 100644 --- a/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIUndoSnapshotCreationMsg.java @@ -5,6 +5,7 @@ import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.vm.metadata.MetadataImpact; /** * @ Author : yh.w @@ -16,6 +17,7 @@ method = HttpMethod.PUT, responseClass = APIUndoSnapshotCreationEvent.class ) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIUndoSnapshotCreationMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class) diff --git a/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java b/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java index 785cf824abd..8e7f3da65fc 100755 --- a/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java +++ b/header/src/main/java/org/zstack/header/volume/APIUpdateVolumeMsg.java @@ -4,6 +4,7 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; /** * Created by frank on 6/14/2015. @@ -14,6 +15,7 @@ responseClass = APIUpdateVolumeEvent.class, isAction = true ) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "VolumeBasedVmUuidFromApiResolver") public class APIUpdateVolumeMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class) private String uuid; diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java index e1864931171..6f2316fa9cf 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/APILocalStorageMigrateVolumeMsg.java @@ -10,6 +10,7 @@ import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.primary.PrimaryStorageMessage; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.storage.primary.PrimaryStorageVO; import org.zstack.header.volume.VolumeVO; @@ -25,6 +26,7 @@ isAction = true ) @DefaultTimeout(timeunit = TimeUnit.HOURS, value = 24) +@MetadataImpact(value = MetadataImpact.Impact.STORAGE, resolver = "VolumeBasedVmUuidFromApiResolver", updateOnFailure = true) public class APILocalStorageMigrateVolumeMsg extends APIMessage implements PrimaryStorageMessage, APIAuditor { @APIParam(resourceType = VolumeVO.class) private String volumeUuid; 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..e2f5e82ff48 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; @@ -39,6 +40,8 @@ import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType; import org.zstack.header.storage.snapshot.*; import org.zstack.header.vm.*; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.vo.ResourceVO; import org.zstack.header.volume.*; import org.zstack.storage.primary.*; @@ -902,6 +905,8 @@ public void handleLocalMessage(Message msg) { handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) { handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { + handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg); } else { super.handleLocalMessage(msg); } @@ -1496,6 +1501,24 @@ protected String scripts() { return huuid; } + /** + * Resolve the target host for local storage operations. + * Priority: rootVolumeUuid (via LocalStorageResourceRefVO) > fallbackHostUuid. + * Returns null if neither can determine a host. + */ + private String resolveHostUuid(String rootVolumeUuid, String fallbackHostUuid) { + if (rootVolumeUuid != null) { + String hostUuid = Q.New(LocalStorageResourceRefVO.class) + .select(LocalStorageResourceRefVO_.hostUuid) + .eq(LocalStorageResourceRefVO_.resourceUuid, rootVolumeUuid) + .findValue(); + if (hostUuid != null) { + return hostUuid; + } + } + return fallbackHostUuid; + } + @Override protected void handle(final DeleteSnapshotOnPrimaryStorageMsg msg) { final String hostUuid = getHostUuidByResourceUuid(msg.getSnapshot().getUuid()); @@ -1640,6 +1663,24 @@ public void fail(ErrorCode errorCode) { }); } + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(msg.getHostUuid()); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, msg.getHostUuid(), new ReturnValueCompletion(msg) { + @Override + public void success(RebaseVolumeBackingFileOnPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + } + + @Override + public void fail(ErrorCode errorCode) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + private void handle(RemoveHostFromLocalStorageMsg msg) { RemoveHostFromLocalStorageReply reply = new RemoveHostFromLocalStorageReply(); thdf.chainSubmit(new ChainTask(msg) { @@ -3329,4 +3370,156 @@ public void fail(ErrorCode errorCode) { public static class LocalStoragePhysicalCapacityUsage extends PrimaryStorageBase.PhysicalCapacityUsage { public long localStorageUsedSize; } + + @Override + protected void handle(final UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + 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) { + final String hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg, chain) { + @Override + public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(errorCode); + bus.reply(msg, reply); + chain.next(); + } + }); + } + + @Override + public String getName() { + return "update-metadata-on-ps-" + self.getUuid(); + } + }); + } + + @Override + protected void handle(final GetVmInstanceMetadataFromPrimaryStorageMsg msg) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + + String hostUuid = resolveHostUuid(msg.getRootVolumeUuid(), msg.getHostUuid()); + if (hostUuid == null) { + reply.setError(operr("cannot determine host for metadata get on local primary storage[uuid:%s]," + + " rootVolumeUuid and hostUuid are both absent", self.getUuid())); + bus.reply(msg, reply); + return; + } + + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(GetVmInstanceMetadataFromPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(final ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { + ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + + List connectedHostUuids = SQL.New( + "select h.hostUuid from LocalStorageHostRefVO h, HostVO host" + + " where h.primaryStorageUuid = :psUuid" + + " and h.hostUuid = host.uuid" + + " and host.status = :hstatus", String.class) + .param("psUuid", self.getUuid()) + .param("hstatus", HostStatus.Connected) + .list(); + if (connectedHostUuids.isEmpty()) { + reply.setError(operr("no connected host found for local primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + + List allSummaries = Collections.synchronizedList(new ArrayList<>()); + + new While<>(connectedHostUuids).all((hostUuid, com) -> { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, hostUuid, new ReturnValueCompletion(com) { + @Override + public void success(ScanVmInstanceMetadataFromPrimaryStorageReply returnValue) { + if (returnValue.getVmInstanceMetadata() != null) { + allSummaries.addAll(returnValue.getVmInstanceMetadata()); + } + com.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to scan vm metadata from host[uuid:%s] on local primary storage[uuid:%s]: %s", + hostUuid, self.getUuid(), errorCode)); + com.addError(errorCode); + com.done(); + } + }); + }).run(new WhileDoneCompletion(msg) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty() && errorCodeList.getCauses().size() == connectedHostUuids.size()) { + reply.setError(operr("failed to scan vm metadata from all hosts on local primary storage[uuid:%s], causes: %s", + self.getUuid(), errorCodeList)); + } else { + reply.setVmInstanceMetadata(new ArrayList<>(allSummaries)); + } + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(final CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + + String hostUuid = resolveHostUuid(msg.getRootVolumeUuid(), null); + if (hostUuid == null) { + reply.setError(operr("cannot determine host for metadata cleanup on local primary storage[uuid:%s]," + + " rootVolumeUuid is absent or has no LocalStorageResourceRefVO", self.getUuid())); + bus.reply(msg, reply); + return; + } + + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + LocalStorageHypervisorBackend bkd = f.getHypervisorBackend(self); + bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + } + + @Override + public void fail(ErrorCode errorCode) { + 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..751e6e24c87 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,12 @@ import org.zstack.header.image.ImageInventory; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageReply; +import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageMsg; +import org.zstack.header.storage.primary.GetVmInstanceMetadataFromPrimaryStorageReply; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.*; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg; import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply; @@ -121,4 +127,14 @@ 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); + + abstract void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + abstract void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + abstract void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + abstract void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg 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..729951f8c5d 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 @@ -42,11 +42,15 @@ import org.zstack.header.rest.RESTFacade; import org.zstack.header.storage.backup.*; import org.zstack.header.storage.primary.*; -import org.zstack.header.storage.snapshot.*; +import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; +import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO; 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.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.*; import org.zstack.identity.AccountManager; import org.zstack.kvm.*; @@ -67,9 +71,7 @@ import java.util.*; import java.util.stream.Collectors; -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.core.Platform.*; import static org.zstack.utils.CollectionDSL.list; import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; @@ -903,6 +905,52 @@ public void setHashValue(String hashValue) { } } + public static class WriteVmMetadataCmd extends AgentCommand { + public String metadata; + public String metadataPath; + public String vmUuid; + public String vmName; + public String vmCategory; + public String architecture; + public String schemaVersion; + } + + public static class WriteVmMetadataRsp extends AgentResponse { + } + + public static class GetVmInstanceMetadataCmd extends AgentCommand { + public String metadataPath; + } + + public static class GetVmInstanceMetadataRsp extends AgentResponse { + public String metadata; + } + + public static class ScanVmMetadataCmd extends AgentCommand { + public String metadataDir; + } + + public static class ScanVmMetadataRsp extends AgentResponse { + public List metadataEntries = new ArrayList<>(); + } + + public static class CleanupVmMetadataCmd extends AgentCommand { + public String metadataPath; + } + + public static class CleanupVmMetadataRsp extends AgentResponse { + } + + public static class PrefixRebaseBackingFilesCmd extends LocalStorageKvmBackend.AgentCommand { + public List filePaths; + public String oldPrefix; + public String newPrefix; + } + + public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.AgentResponse { + public int rebasedCount; + } + public static final String INIT_PATH = "/localstorage/init"; public static final String GET_PHYSICAL_CAPACITY_PATH = "/localstorage/getphysicalcapacity"; public static final String CREATE_EMPTY_VOLUME_PATH = "/localstorage/volume/createempty"; @@ -935,6 +983,11 @@ public void setHashValue(String hashValue) { public static final String CANCEL_DOWNLOAD_BITS_FROM_KVM_HOST_PATH = "/localstorage/kvmhost/download/cancel"; public static final String GET_DOWNLOAD_BITS_FROM_KVM_HOST_PROGRESS_PATH = "/localstorage/kvmhost/download/progress"; public static final String GET_QCOW2_HASH_VALUE_PATH = "/localstorage/getqcow2hash"; + public static final String WRITE_VM_METADATA_PATH = "/localstorage/vm/metadata/write"; + public static final String GET_VM_INSTANCE_METADATA_PATH = "/localstorage/vm/metadata/get"; + public static final String SCAN_VM_METADATA_PATH = "/localstorage/vm/metadata/scan"; + public static final String CLEANUP_VM_METADATA_PATH = "/localstorage/vm/metadata/cleanup"; + public static final String PREFIX_REBASE_BACKING_FILES_PATH = "/localstorage/snapshot/prefixrebasebackingfiles"; public LocalStorageKvmBackend() { } @@ -3797,4 +3850,107 @@ public void fail(ErrorCode errorCode) { } }); } + + @Override + void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + WriteVmMetadataCmd cmd = new WriteVmMetadataCmd(); + cmd.metadata = msg.getMetadata(); + cmd.metadataPath = msg.getMetadataPath(); + cmd.vmUuid = msg.getVmInstanceUuid(); + cmd.vmName = msg.getVmInstanceName(); + cmd.vmCategory = msg.getVmCategory(); + cmd.architecture = msg.getArchitecture(); + cmd.schemaVersion = msg.getSchemaVersion(); + httpCall(WRITE_VM_METADATA_PATH, hostUuid, cmd, WriteVmMetadataRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(WriteVmMetadataRsp rsp) { + completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + GetVmInstanceMetadataCmd cmd = new GetVmInstanceMetadataCmd(); + cmd.metadataPath = msg.getMetadataPath(); + httpCall(GET_VM_INSTANCE_METADATA_PATH, hostUuid, cmd, GetVmInstanceMetadataRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(GetVmInstanceMetadataRsp rsp) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + reply.setMetadata(rsp.metadata); + completion.success(reply); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + ScanVmMetadataCmd cmd = new ScanVmMetadataCmd(); + cmd.metadataDir = msg.getMetadataDir(); + httpCall(SCAN_VM_METADATA_PATH, hostUuid, cmd, ScanVmMetadataRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(ScanVmMetadataRsp rsp) { + ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + rsp.metadataEntries.forEach(entry -> entry.setHostUuid(hostUuid)); + reply.setVmInstanceMetadata(rsp.metadataEntries); + completion.success(reply); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); + cmd.metadataPath = msg.getMetadataPath(); + + httpCall(CLEANUP_VM_METADATA_PATH, hostUuid, cmd, CleanupVmMetadataRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(CleanupVmMetadataRsp rsp) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + completion.success(reply); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + PrefixRebaseBackingFilesCmd cmd = new PrefixRebaseBackingFilesCmd(); + cmd.filePaths = msg.getInstallPaths(); + cmd.oldPrefix = msg.getOldPrefix(); + cmd.newPrefix = msg.getNewPrefix(); + + httpCall(PREFIX_REBASE_BACKING_FILES_PATH, hostUuid, cmd, PrefixRebaseBackingFilesRsp.class, new ReturnValueCompletion(completion) { + @Override + public void success(PrefixRebaseBackingFilesRsp rsp) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + reply.setRebasedCount(rsp.rebasedCount); + completion.success(reply); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java index dbe774888f0..3edb2e916db 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulator.java @@ -334,6 +334,44 @@ String offlineMerge(HttpEntity entity) { return null; } + @RequestMapping(value=LocalStorageKvmBackend.WRITE_VM_METADATA_PATH, method= RequestMethod.POST) + public @ResponseBody + String writeVmMetadata(HttpEntity entity) { + WriteVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), WriteVmMetadataCmd.class); + config.writeVmMetadataCmds.add(cmd); + reply(entity, new WriteVmMetadataRsp()); + return null; + } + + @RequestMapping(value=LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH, method= RequestMethod.POST) + public @ResponseBody + String getVmInstanceMetadata(HttpEntity entity) { + GetVmInstanceMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetVmInstanceMetadataCmd.class); + config.getVmInstanceMetadataCmds.add(cmd); + GetVmInstanceMetadataRsp rsp = new GetVmInstanceMetadataRsp(); + reply(entity, rsp); + return null; + } + + @RequestMapping(value=LocalStorageKvmBackend.SCAN_VM_METADATA_PATH, method= RequestMethod.POST) + public @ResponseBody + String scanVmMetadata(HttpEntity entity) { + ScanVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), ScanVmMetadataCmd.class); + config.scanVmMetadataCmds.add(cmd); + ScanVmMetadataRsp rsp = new ScanVmMetadataRsp(); + reply(entity, rsp); + return null; + } + + @RequestMapping(value=LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH, method= RequestMethod.POST) + public @ResponseBody + String cleanupVmMetadata(HttpEntity entity) { + CleanupVmMetadataCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CleanupVmMetadataCmd.class); + config.cleanupVmMetadataCmds.add(cmd); + reply(entity, new CleanupVmMetadataRsp()); + return null; + } + @ExceptionHandler(Exception.class) public ModelAndView handleAllException(Exception ex) { logger.warn(ex.getMessage(), ex); diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java index 53a02339833..34463b46c6e 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageSimulatorConfig.java @@ -59,4 +59,9 @@ public static class Capacity { public List getQCOW2ReferenceCmds = new ArrayList<>(); public List getQCOW2ReferenceCmdReference = new ArrayList<>(); + + public List writeVmMetadataCmds = new ArrayList<>(); + public List getVmInstanceMetadataCmds = new ArrayList<>(); + public List scanVmMetadataCmds = new ArrayList<>(); + public List cleanupVmMetadataCmds = new ArrayList<>(); } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java new file mode 100644 index 00000000000..767657dab6b --- /dev/null +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java @@ -0,0 +1,127 @@ +package org.zstack.storage.primary.local; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.header.exception.CloudRuntimeException; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.storage.primary.PrimaryStorageVO_; +import org.zstack.header.vm.metadata.VmInstanceMetadataConstants; +import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint; +import org.zstack.header.vm.metadata.VmMetadataPathReplacementExtensionPoint; +import org.zstack.header.vm.metadata.VmMetadataResourcePersistExtensionPoint; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.sql.Timestamp; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class LocalStorageVmMetadataExtension implements VmMetadataPathBuildExtensionPoint, + VmMetadataPathReplacementExtensionPoint, VmMetadataResourcePersistExtensionPoint { + private static final CLogger logger = Utils.getLogger(LocalStorageVmMetadataExtension.class); + + @Autowired + private DatabaseFacade dbf; + + @Override + public String getPrimaryStorageType() { + return LocalStorageConstants.LOCAL_STORAGE_TYPE; + } + + @Override + public String buildMetadataDir(String primaryStorageUuid) { + String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + if (url == null) { + throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid)); + } + return String.format("%s/%s", url, VmInstanceMetadataConstants.METADATA_DIR_NAME); + } + + @Override + public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) { + String url = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + if (url == null) { + throw new CloudRuntimeException(String.format("cannot find url for primary storage[uuid:%s]", primaryStorageUuid)); + } + return String.format("%s/%s/%s%s", url, VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX); + } + + @Override + public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) { + String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); + if (baseDir == null) { + logger.warn(String.format("LocalStorage PS[uuid:%s] has no url, path replacement disabled", targetPsUuid)); + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(Collections.emptyMap()); + return result; + } + String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/"; + + // Extract old prefix from the first recognizable path + String oldPrefix = null; + for (String path : allOldPaths) { + oldPrefix = extractOldPrefix(path); + if (oldPrefix != null) break; + } + + Map pathMap = new LinkedHashMap<>(); + if (oldPrefix != null) { + for (String oldPath : allOldPaths) { + if (oldPath != null && oldPath.startsWith(oldPrefix)) { + pathMap.put(oldPath, newPrefix + oldPath.substring(oldPrefix.length())); + } + } + } else { + logger.warn(String.format("cannot extract old path prefix from any path for LocalStorage PS[uuid:%s], " + + "path replacement disabled", targetPsUuid)); + } + + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(pathMap); + result.setOldPrefix(oldPrefix); + result.setNewPrefix(newPrefix); + return result; + } + + private String extractOldPrefix(String path) { + if (path == null || !path.startsWith("/")) { + return null; + } + String[] markers = {"/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/"}; + for (String marker : markers) { + int idx = path.indexOf(marker); + if (idx > 0) { + return path.substring(0, idx + 1); + } + } + return null; + } + + @Override + public void afterVolumePersist(String primaryStorageUuid, String resourceUuid, + String resourceType, String hostUuid, long size, Timestamp now) { + createResourceRef(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now); + } + + @Override + public void afterSnapshotPersist(String primaryStorageUuid, String resourceUuid, + String resourceType, String hostUuid, long size, Timestamp now) { + createResourceRef(primaryStorageUuid, resourceUuid, resourceType, hostUuid, size, now); + } + + private void createResourceRef(String primaryStorageUuid, String resourceUuid, + String resourceType, String hostUuid, long size, Timestamp now) { + LocalStorageResourceRefVO ref = new LocalStorageResourceRefVO(); + ref.setPrimaryStorageUuid(primaryStorageUuid); + ref.setResourceUuid(resourceUuid); + ref.setResourceType(resourceType); + ref.setHostUuid(hostUuid); + ref.setSize(size); + ref.setCreateDate(now); + ref.setLastOpDate(now); + dbf.persist(ref); + } +} 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..a70c24c3939 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 @@ -36,14 +36,14 @@ import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.primary.VolumeSnapshotCapability.VolumeSnapshotArrangementType; -import org.zstack.header.storage.snapshot.DeleteVolumeSnapshotDirection; 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.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.*; import org.zstack.kvm.*; import org.zstack.storage.primary.*; @@ -51,10 +51,8 @@ import org.zstack.storage.volume.VolumeErrors; import org.zstack.storage.volume.VolumeSystemTags; import org.zstack.tag.SystemTagCreator; -import org.zstack.utils.CollectionUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; -import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import javax.persistence.Tuple; @@ -131,6 +129,8 @@ protected void handleLocalMessage(Message msg) { handle((CommitVolumeSnapshotOnPrimaryStorageMsg) msg); } else if (msg instanceof PullVolumeSnapshotOnPrimaryStorageMsg) { handle((PullVolumeSnapshotOnPrimaryStorageMsg) msg); + } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { + handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg); } else { super.handleLocalMessage(msg); } @@ -1924,4 +1924,170 @@ private String getHostUuidFromVolume(String volumeUuid) { return hostUuid; } + + @Override + protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + 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) { + 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); + chain.next(); + return; + } + + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg, chain) { + @Override + public void success(UpdateVmInstanceMetadataOnPrimaryStorageReply r) { + bus.reply(msg, r); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + chain.next(); + } + }); + } + + @Override + public String getName() { + return "update-metadata-on-ps-" + self.getUuid(); + } + }); + } + + @Override + protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + + List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + if (connectedHosts.isEmpty()) { + reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + + String hostUuid = connectedHosts.get(0).getUuid(); + final NfsPrimaryStorageBackend backend = getUsableBackend(); + + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(GetVmInstanceMetadataFromPrimaryStorageReply r) { + bus.reply(msg, r); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { + ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + + List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + if (connectedHosts.isEmpty()) { + reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + + String hostUuid = connectedHosts.get(0).getUuid(); + final NfsPrimaryStorageBackend backend = getUsableBackend(); + + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(ScanVmInstanceMetadataFromPrimaryStorageReply r) { + bus.reply(msg, r); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + + List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + if (connectedHosts.isEmpty()) { + reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + + String hostUuid = connectedHosts.get(0).getUuid(); + final NfsPrimaryStorageBackend backend = getUsableBackend(); + + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply r) { + bus.reply(msg, r); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + + NfsPrimaryStorageBackend backend = getUsableBackend(); + if (backend == null) { + reply.setError(operr("the NFS primary storage[uuid:%s, name:%s] cannot find hosts in attached clusters to perform the operation", + self.getUuid(), self.getName())); + bus.reply(msg, reply); + return; + } + + List connectedHosts = factory.getConnectedHostForOperation(getSelfInventory()); + if (connectedHosts.isEmpty()) { + reply.setError(operr("no connected host found for NFS primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + String hostUuid = connectedHosts.get(0).getUuid(); + + backend.handle(msg, hostUuid, new ReturnValueCompletion(msg) { + @Override + public void success(RebaseVolumeBackingFileOnPrimaryStorageReply 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..35a240a6221 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.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.VolumeStats; import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg; import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply; @@ -91,6 +93,16 @@ public interface NfsPrimaryStorageBackend { void updateMountPoint(PrimaryStorageInventory pinv, String clusterUuid, String oldMountPoint, String newMountPoint, Completion completion); + void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion); + + void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg 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..cd3be075aee 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 @@ -21,7 +21,9 @@ import org.zstack.core.timeout.ApiTimeoutManager; import org.zstack.core.trash.StorageTrash; import org.zstack.header.core.*; -import org.zstack.header.core.workflow.*; +import org.zstack.header.core.workflow.Flow; +import org.zstack.header.core.workflow.FlowTrigger; +import org.zstack.header.core.workflow.NoRollbackFlow; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.errorcode.OperationFailureException; @@ -39,6 +41,8 @@ import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.vm.metadata.UpdateVmInstanceMetadataOnPrimaryStorageReply; import org.zstack.header.volume.*; import org.zstack.identity.AccountManager; import org.zstack.kvm.*; @@ -129,7 +133,12 @@ public class NfsPrimaryStorageKVMBackend implements NfsPrimaryStorageBackend, public static final String GET_DOWNLOAD_BITS_FROM_KVM_HOST_PROGRESS_PATH = "/nfsprimarystorage/kvmhost/download/progress"; public static final String CREATE_VOLUME_FROM_TEMPLATE_PATH = "/nfsprimarystorage/sftp/createvolumefromtemplate"; public static final String GET_QCOW2_HASH_VALUE_PATH = "/nfsprimarystorage/getqcow2hash"; + public static final String WRITE_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/write"; + public static final String GET_VM_INSTANCE_METADATA_PATH = "/nfsprimarystorage/vm/metadata/get"; + public static final String SCAN_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/scan"; + public static final String CLEANUP_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/cleanup"; + public static final String NFS_PREFIX_REBASE_BACKING_FILES_PATH = "/nfsprimarystorage/snapshot/prefixrebasebackingfiles"; //////////////// For unit test ////////////////////////// private boolean syncGetCapacity = false; @@ -2051,4 +2060,166 @@ public void run(MessageReply r) { } }); } + + public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + WriteVmMetadataCmd cmd = new WriteVmMetadataCmd(); + cmd.metadata = msg.getMetadata(); + cmd.metadataPath = msg.getMetadataPath(); + cmd.vmUuid = msg.getVmInstanceUuid(); + cmd.vmName = msg.getVmInstanceName(); + cmd.vmCategory = msg.getVmCategory(); + cmd.architecture = msg.getArchitecture(); + cmd.schemaVersion = msg.getSchemaVersion(); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(WRITE_VM_METADATA_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(hmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + WriteVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(WriteVmMetadataRsp.class); + if (!rsp.isSuccess()) { + completion.fail(operr("failed to write vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); + return; + } + + completion.success(new UpdateVmInstanceMetadataOnPrimaryStorageReply()); + } + }); + } + + @Override + public void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataCmd(); + cmd.metadataPath = msg.getMetadataPath(); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(GET_VM_INSTANCE_METADATA_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(hmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + GetVmInstanceMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(GetVmInstanceMetadataRsp.class); + if (!rsp.isSuccess()) { + completion.fail(operr("failed to get vm instance metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); + return; + } + + GetVmInstanceMetadataFromPrimaryStorageReply r = new GetVmInstanceMetadataFromPrimaryStorageReply(); + r.setMetadata(rsp.metadata); + completion.success(r); + } + }); + } + + @Override + public void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd cmd = new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataCmd(); + cmd.metadataDir = msg.getMetadataDir(); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(SCAN_VM_METADATA_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(hmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp.class); + if (!rsp.isSuccess()) { + completion.fail(operr("failed to scan vm instance metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); + return; + } + + ScanVmInstanceMetadataFromPrimaryStorageReply r = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + r.setVmInstanceMetadata(rsp.metadataEntries); + completion.success(r); + } + }); + } + + @Override + public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); + cmd.metadataPath = msg.getMetadataPath(); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(CLEANUP_VM_METADATA_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(hmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + CleanupVmMetadataRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(CleanupVmMetadataRsp.class); + if (!rsp.isSuccess()) { + completion.fail(operr("failed to cleanup vm metadata on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); + return; + } + + CleanupVmInstanceMetadataOnPrimaryStorageReply r = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + completion.success(r); + } + }); + } + + @Override + public void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesCmd cmd = new NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesCmd(); + // NFS installPaths are already filesystem paths, no conversion needed + cmd.filePaths = msg.getInstallPaths(); + cmd.oldPrefix = msg.getOldPrefix(); + cmd.newPrefix = msg.getNewPrefix(); + cmd.setUuid(msg.getPrimaryStorageUuid()); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(NFS_PREFIX_REBASE_BACKING_FILES_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(hmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + completion.fail(reply.getError()); + return; + } + + NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesRsp rsp = + ((KVMHostAsyncHttpCallReply) reply).toResponse(NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesRsp.class); + if (!rsp.isSuccess()) { + completion.fail(operr("failed to prefix rebase backing files on nfs via host[uuid:%s]: %s", hostUuid, rsp.getError())); + return; + } + + RebaseVolumeBackingFileOnPrimaryStorageReply r = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + r.setRebasedCount(rsp.rebasedCount); + completion.success(r); + } + }); + } } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java index d7cebfaa4c6..12c41c054c7 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java @@ -2,6 +2,7 @@ import org.zstack.header.HasThreadContext; import org.zstack.header.core.validation.Validation; +import org.zstack.header.storage.primary.VmMetadataScanEntry; import org.zstack.kvm.KVMAgentCommands; import org.zstack.kvm.KVMAgentCommands.AgentCommand; import org.zstack.kvm.KVMAgentCommands.AgentResponse; @@ -948,4 +949,51 @@ public void setHashValue(String hashValue) { this.hashValue = hashValue; } } + + public static class WriteVmMetadataCmd extends NfsPrimaryStorageAgentCommand { + public String metadata; + public String metadataPath; + public String vmUuid; + public String vmName; + public String vmCategory; + public String architecture; + public String schemaVersion; + } + + public static class WriteVmMetadataRsp extends NfsPrimaryStorageAgentResponse { + } + + + public static class GetVmInstanceMetadataCmd extends NfsPrimaryStorageAgentCommand { + public String metadataPath; + } + + public static class GetVmInstanceMetadataRsp extends NfsPrimaryStorageAgentResponse { + public String metadata; + } + + public static class ScanVmMetadataCmd extends NfsPrimaryStorageAgentCommand { + public String metadataDir; + } + + public static class ScanVmMetadataRsp extends NfsPrimaryStorageAgentResponse { + public List metadataEntries = new ArrayList<>(); + } + + public static class CleanupVmMetadataCmd extends NfsPrimaryStorageAgentCommand { + public String metadataPath; + } + + public static class CleanupVmMetadataRsp extends NfsPrimaryStorageAgentResponse { + } + + public static class PrefixRebaseBackingFilesCmd extends NfsPrimaryStorageAgentCommand { + public List filePaths; + public String oldPrefix; + public String newPrefix; + } + + public static class PrefixRebaseBackingFilesRsp extends NfsPrimaryStorageAgentResponse { + public int rebasedCount; + } } diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java new file mode 100644 index 00000000000..beee1a20371 --- /dev/null +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java @@ -0,0 +1,90 @@ +package org.zstack.storage.primary.nfs; + +import org.zstack.core.db.Q; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.storage.primary.PrimaryStorageVO_; +import org.zstack.header.vm.metadata.VmInstanceMetadataConstants; +import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint; +import org.zstack.header.vm.metadata.VmMetadataPathReplacementExtensionPoint; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class NfsVmMetadataExtension implements VmMetadataPathBuildExtensionPoint, VmMetadataPathReplacementExtensionPoint { + private static final CLogger logger = Utils.getLogger(NfsVmMetadataExtension.class); + + @Override + public String getPrimaryStorageType() { + return NfsPrimaryStorageConstant.NFS_PRIMARY_STORAGE_TYPE; + } + + @Override + public String buildMetadataDir(String primaryStorageUuid) { + String mountPath = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + return String.format("%s/%s", mountPath, VmInstanceMetadataConstants.METADATA_DIR_NAME); + } + + @Override + public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) { + String mountPath = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + return String.format("%s/%s/%s.json", mountPath, VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid); + } + + @Override + public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) { + String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); + if (baseDir == null) { + baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.url).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); + } + if (baseDir == null) { + logger.warn(String.format("NFS PS[uuid:%s] has no mountPath or url, path replacement disabled", targetPsUuid)); + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(Collections.emptyMap()); + return result; + } + String newPrefix = baseDir.endsWith("/") ? baseDir : baseDir + "/"; + + // Extract old prefix from the first recognizable path + String oldPrefix = null; + for (String path : allOldPaths) { + oldPrefix = extractOldPrefix(path); + if (oldPrefix != null) break; + } + + Map pathMap = new LinkedHashMap<>(); + if (oldPrefix != null) { + for (String oldPath : allOldPaths) { + if (oldPath != null && oldPath.startsWith(oldPrefix)) { + pathMap.put(oldPath, newPrefix + oldPath.substring(oldPrefix.length())); + } + } + } else { + logger.warn(String.format("cannot extract old path prefix from any path for NFS PS[uuid:%s], " + + "path replacement disabled", targetPsUuid)); + } + + PathReplacementResult result = new PathReplacementResult(); + result.setPathMap(pathMap); + result.setOldPrefix(oldPrefix); + result.setNewPrefix(newPrefix); + return result; + } + + private String extractOldPrefix(String path) { + if (path == null || !path.startsWith("/")) { + return null; + } + String[] markers = {"/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/"}; + for (String marker : markers) { + int idx = path.indexOf(marker); + if (idx > 0) { + return path.substring(0, idx + 1); + } + } + return null; + } +} diff --git a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java index fd43d66dac3..6bf601508ec 100644 --- a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java +++ b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java @@ -5,11 +5,13 @@ import org.zstack.header.message.APIDeleteMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.vo.ResourceVO; @RestRequest(path = "/resource-configurations/{category}/{name}/{resourceUuid}", method = HttpMethod.DELETE, responseClass = APIDeleteResourceConfigEvent.class) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APIDeleteResourceConfigMsg extends APIDeleteMessage implements ResourceConfigMessage { @APIParam private String category; diff --git a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java index 1b8437d2404..3f5eb20db52 100644 --- a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java +++ b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIUpdateResourceConfigMsg.java @@ -5,12 +5,14 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.APIParam; import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.metadata.MetadataImpact; import org.zstack.header.vo.ResourceVO; @RestRequest(path = "/resource-configurations/{category}/{name}/{resourceUuid}/actions", method = HttpMethod.PUT, isAction = true, responseClass = APIUpdateResourceConfigEvent.class) +@MetadataImpact(value = MetadataImpact.Impact.CONFIG, resolver = "ResourceBasedVmUuidFromApiResolver") public class APIUpdateResourceConfigMsg extends APIMessage implements ResourceConfigMessage { @APIParam private String category; diff --git a/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java new file mode 100644 index 00000000000..46586cab3c4 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CleanupVmInstanceMetadataAction 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.CleanupVmInstanceMetadataResult 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 = true, nullElements = false, emptyString = true, noTrim = false) + public java.util.List vmUuids; + + @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.CleanupVmInstanceMetadataResult value = res.getResult(org.zstack.sdk.CleanupVmInstanceMetadataResult.class); + ret.value = value == null ? new org.zstack.sdk.CleanupVmInstanceMetadataResult() : 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 = "PUT"; + info.path = "/vm-instances/metadata/cleanup"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "cleanupVmInstanceMetadata"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java new file mode 100644 index 00000000000..879a409cc59 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CleanupVmInstanceMetadataResult.java @@ -0,0 +1,30 @@ +package org.zstack.sdk; + + + +public class CleanupVmInstanceMetadataResult { + public java.lang.Integer totalCleaned; + public void setTotalCleaned(java.lang.Integer totalCleaned) { + this.totalCleaned = totalCleaned; + } + public java.lang.Integer getTotalCleaned() { + return this.totalCleaned; + } + + public java.lang.Integer totalFailed; + public void setTotalFailed(java.lang.Integer totalFailed) { + this.totalFailed = totalFailed; + } + public java.lang.Integer getTotalFailed() { + return this.totalFailed; + } + + public java.util.List failedVmUuids; + public void setFailedVmUuids(java.util.List failedVmUuids) { + this.failedVmUuids = failedVmUuids; + } + public java.util.List getFailedVmUuids() { + return this.failedVmUuids; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java new file mode 100644 index 00000000000..0471b4b7610 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java @@ -0,0 +1,95 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class GetVmInstanceMetadataFromPrimaryStorageAction 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.GetVmInstanceMetadataFromPrimaryStorageResult 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 uuid; + + @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; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult value = res.getResult(org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult.class); + ret.value = value == null ? new org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageResult() : 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 = "GET"; + info.path = "/primary-storage/vm-instances/metadata"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java new file mode 100644 index 00000000000..d22099fae76 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + + + +public class GetVmInstanceMetadataFromPrimaryStorageResult { + public java.lang.String metadata; + public void setMetadata(java.lang.String metadata) { + this.metadata = metadata; + } + public java.lang.String getMetadata() { + return this.metadata; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java new file mode 100644 index 00000000000..c7acac2ae1f --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java @@ -0,0 +1,119 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RegisterVmInstanceFromMetadataAction 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.RegisterVmInstanceFromMetadataResult 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 metadataPath; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String primaryStorageUuid; + + @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 clusterUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String name; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.List tagUuids; + + @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.RegisterVmInstanceFromMetadataResult value = res.getResult(org.zstack.sdk.RegisterVmInstanceFromMetadataResult.class); + ret.value = value == null ? new org.zstack.sdk.RegisterVmInstanceFromMetadataResult() : 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/metadata/register"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java new file mode 100644 index 00000000000..11634dcf3f1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmInstanceInventory; + +public class RegisterVmInstanceFromMetadataResult { + public VmInstanceInventory inventory; + public void setInventory(VmInstanceInventory inventory) { + this.inventory = inventory; + } + public VmInstanceInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java new file mode 100644 index 00000000000..64984c03149 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageAction.java @@ -0,0 +1,95 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ScanVmInstanceMetadataFromPrimaryStorageAction 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.ScanVmInstanceMetadataFromPrimaryStorageResult 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 uuid; + + @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; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult value = res.getResult(org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult.class); + ret.value = value == null ? new org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageResult() : 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 = "GET"; + info.path = "/primary-storage/vm-instances/metadata/scan"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java new file mode 100644 index 00000000000..4abb18ceae6 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ScanVmInstanceMetadataFromPrimaryStorageResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + + + +public class ScanVmInstanceMetadataFromPrimaryStorageResult { + public java.util.List vmInstanceMetadata; + public void setVmInstanceMetadata(java.util.List vmInstanceMetadata) { + this.vmInstanceMetadata = vmInstanceMetadata; + } + public java.util.List getVmInstanceMetadata() { + return this.vmInstanceMetadata; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java new file mode 100644 index 00000000000..bd554a6bcef --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java @@ -0,0 +1,100 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; + +public class UpdateVmInstanceMetadataAction 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.UpdateVmInstanceMetadataResult 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 = true, nullElements = false, emptyString = true, noTrim = false) + public java.util.List vmUuids; + + @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.UpdateVmInstanceMetadataResult value = res.getResult(org.zstack.sdk.UpdateVmInstanceMetadataResult.class); + ret.value = value == null ? new org.zstack.sdk.UpdateVmInstanceMetadataResult() : 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 = "PUT"; + info.path = "/vm-instances/metadata/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "updateVmMetadata"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java new file mode 100644 index 00000000000..d09f1fe7bf7 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class UpdateVmInstanceMetadataResult { + +} 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..52d4f78f805 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.transaction.annotation.Transactional; +import org.zstack.core.Platform; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; import org.zstack.core.cascade.CascadeFacade; @@ -50,6 +51,7 @@ import org.zstack.header.storage.primary.PrimaryStorageCanonicalEvent.PrimaryStorageStatusChangedData; import org.zstack.header.storage.snapshot.*; import org.zstack.header.vm.*; +import org.zstack.header.vm.metadata.*; import org.zstack.header.volume.*; import org.zstack.storage.volume.VolumeUtils; import org.zstack.utils.CollectionDSL; @@ -417,6 +419,16 @@ protected void handleLocalMessage(Message msg) { handle((DeleteVolumeChainOnPrimaryStorageMsg) msg); } else if (msg instanceof CleanUpStorageTrashOnPrimaryStorageMsg) { handle((CleanUpStorageTrashOnPrimaryStorageMsg)msg); + } else if (msg instanceof UpdateVmInstanceMetadataOnPrimaryStorageMsg) { + handle((UpdateVmInstanceMetadataOnPrimaryStorageMsg) msg); + } else if (msg instanceof ScanVmInstanceMetadataFromPrimaryStorageMsg) { + handle((ScanVmInstanceMetadataFromPrimaryStorageMsg) msg); + } else if (msg instanceof GetVmInstanceMetadataFromPrimaryStorageMsg) { + handle((GetVmInstanceMetadataFromPrimaryStorageMsg) msg); + } else if (msg instanceof CleanupVmInstanceMetadataOnPrimaryStorageMsg) { + handle((CleanupVmInstanceMetadataOnPrimaryStorageMsg) msg); + } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { + handle((RebaseVolumeBackingFileOnPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } @@ -935,6 +947,8 @@ protected void handleApiMessage(APIMessage msg) { handle((APICleanUpStorageTrashOnPrimaryStorageMsg) msg); } else if (msg instanceof APIAddStorageProtocolMsg) { handle((APIAddStorageProtocolMsg) msg); + } else if (msg instanceof APIScanVmInstanceMetadataFromPrimaryStorageMsg) { + handle((APIScanVmInstanceMetadataFromPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } @@ -1773,6 +1787,31 @@ protected void handle(UnlinkBitsOnPrimaryStorageMsg msg) { bus.reply(msg, reply); }; + protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + bus.reply(msg, reply); + } + + protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + bus.reply(msg, reply); + } + + protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { + ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + bus.reply(msg, reply); + } + + protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + bus.reply(msg, reply); + } + + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + bus.reply(msg, reply); + } + // don't attach any cluster public boolean isUnmounted() { long count = Q.New(PrimaryStorageClusterRefVO.class) @@ -1812,4 +1851,38 @@ 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(APIScanVmInstanceMetadataFromPrimaryStorageMsg msg) { + APIScanVmInstanceMetadataFromPrimaryStorageReply reply = new APIScanVmInstanceMetadataFromPrimaryStorageReply(); + + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue(); + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + if (ext == null) { + reply.setError(Platform.operr("primary storage type %s does not support metadata", psType)); + bus.reply(msg, reply); + return; + } + String metadataDir = ext.buildMetadataDir(msg.getPrimaryStorageUuid()); + + ScanVmInstanceMetadataFromPrimaryStorageMsg gmsg = new ScanVmInstanceMetadataFromPrimaryStorageMsg(); + gmsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid()); + gmsg.setMetadataDir(metadataDir); + 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; + } + ScanVmInstanceMetadataFromPrimaryStorageReply re = r.castReply(); + List filtered = re.getVmInstanceMetadata().stream() + .filter(e -> !VmMetadataCategory.TEMPLATE_CACHE.toString().equals(e.getVmCategory())) + .collect(Collectors.toList()); + reply.setVmInstanceMetadata(filtered); + 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..91aca4c069e 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -5606,6 +5606,33 @@ abstract class ApiHelper { } + def cleanupVmInstanceMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CleanupVmInstanceMetadataAction.class) Closure c) { + def a = new org.zstack.sdk.CleanupVmInstanceMetadataAction() + 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 cloneVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CloneVmInstanceAction.class) Closure c) { def a = new org.zstack.sdk.CloneVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -6929,33 +6956,6 @@ abstract class ApiHelper { } - def updateHostname(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostnameAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateHostnameAction() - 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 createIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateIPsecConnectionAction.class) Closure c) { def a = new org.zstack.sdk.CreateIPsecConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -18566,6 +18566,33 @@ abstract class ApiHelper { } + def getVmInstanceMetadataFromPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageAction.class) Closure c) { + def a = new org.zstack.sdk.GetVmInstanceMetadataFromPrimaryStorageAction() + 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 getVmInstanceProtectedRecoveryPoints(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetVmInstanceProtectedRecoveryPointsAction.class) Closure c) { def a = new org.zstack.sdk.GetVmInstanceProtectedRecoveryPointsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -27406,6 +27433,33 @@ abstract class ApiHelper { } + def registerVmInstanceFromMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.RegisterVmInstanceFromMetadataAction.class) Closure c) { + def a = new org.zstack.sdk.RegisterVmInstanceFromMetadataAction() + 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 @@ -28621,6 +28675,33 @@ abstract class ApiHelper { } + def scanVmInstanceMetadataFromPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ScanVmInstanceMetadataFromPrimaryStorageAction() + 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 securityMachineDetectSync(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SecurityMachineDetectSyncAction.class) Closure c) { def a = new org.zstack.sdk.SecurityMachineDetectSyncAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -32212,6 +32293,33 @@ abstract class ApiHelper { } + def updateHostname(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostnameAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateHostnameAction() + 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 updateIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateIPsecConnectionAction.class) Closure c) { def a = new org.zstack.sdk.UpdateIPsecConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -34183,6 +34291,33 @@ abstract class ApiHelper { } + def updateVmInstanceMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstanceMetadataAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateVmInstanceMetadataAction() + 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 updateVmNicDriver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmNicDriverAction.class) Closure c) { def a = new org.zstack.sdk.UpdateVmNicDriverAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -38395,8 +38530,8 @@ abstract class ApiHelper { } - def addZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.AddZBoxAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.AddZBoxAction() + def cleanSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38422,8 +38557,8 @@ abstract class ApiHelper { } - def createZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.CreateZBoxBackupAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.CreateZBoxBackupAction() + def getDirectoryUsage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38449,8 +38584,8 @@ abstract class ApiHelper { } - def ejectZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.EjectZBoxAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.EjectZBoxAction() + def getUploadSoftwarePackageJobDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38476,8 +38611,8 @@ abstract class ApiHelper { } - def getZBoxBackupDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.GetZBoxBackupDetailsAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.GetZBoxBackupDetailsAction() + def installSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38503,8 +38638,8 @@ abstract class ApiHelper { } - def queryZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.QueryZBoxAction() + def querySoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38532,15 +38667,13 @@ abstract class ApiHelper { } - def queryZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxBackupAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.QueryZBoxBackupAction() + def uninstallSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - a.conditions = a.conditions.collect { it.toString() } - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -38561,8 +38694,8 @@ abstract class ApiHelper { } - def syncZBoxCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.SyncZBoxCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.zbox.SyncZBoxCapacityAction() + def uploadSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction.class) Closure c) { + def a = new org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38588,25 +38721,26 @@ abstract class ApiHelper { } - def cleanSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.CleanSoftwarePackageAction() + def addZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.AddZBoxAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.AddZBoxAction() 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()) @@ -38614,25 +38748,26 @@ abstract class ApiHelper { } - def getDirectoryUsage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.GetDirectoryUsageAction() + def createZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.CreateZBoxBackupAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.CreateZBoxBackupAction() 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()) @@ -38640,25 +38775,26 @@ abstract class ApiHelper { } - def getUploadSoftwarePackageJobDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.GetUploadSoftwarePackageJobDetailsAction() + def ejectZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.EjectZBoxAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.EjectZBoxAction() 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()) @@ -38666,22 +38802,26 @@ abstract class ApiHelper { } - def installSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.InstallSoftwarePackageAction() + def getZBoxBackupDetails(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.GetZBoxBackupDetailsAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.GetZBoxBackupDetailsAction() 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()) @@ -38689,13 +38829,13 @@ abstract class ApiHelper { } - def querySoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.QuerySoftwarePackageAction() + def queryZBox(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.QueryZBoxAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + a.conditions = a.conditions.collect { it.toString() } @@ -38703,14 +38843,14 @@ abstract class ApiHelper { 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()) @@ -38718,26 +38858,28 @@ abstract class ApiHelper { } - def uninstallSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.UninstallSoftwarePackageAction() + def queryZBoxBackup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.QueryZBoxBackupAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.QueryZBoxBackupAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() + + a.conditions = a.conditions.collect { it.toString() } 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()) @@ -38745,25 +38887,26 @@ abstract class ApiHelper { } - def uploadSoftwarePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction.class) Closure c) { - def a = new org.zstack.sdk.softwarePackage.header.UploadSoftwarePackageAction() + def syncZBoxCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zbox.SyncZBoxCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.zbox.SyncZBoxCapacityAction() 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()) diff --git a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy index 747ca4388b1..ceee209296c 100755 --- a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy @@ -600,6 +600,24 @@ class LocalStorageSpec extends PrimaryStorageSpec { rsp.hashValue = cmd.installPath return rsp } + + simulator(LocalStorageKvmBackend.WRITE_VM_METADATA_PATH) { + return new LocalStorageKvmBackend.WriteVmMetadataRsp() + } + + simulator(LocalStorageKvmBackend.GET_VM_INSTANCE_METADATA_PATH) { + def rsp = new LocalStorageKvmBackend.GetVmInstanceMetadataRsp() + 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 new LocalStorageKvmBackend.GetVmInstanceMetadataRsp() + } + + simulator(LocalStorageKvmBackend.SCAN_VM_METADATA_PATH) { + return new LocalStorageKvmBackend.ScanVmMetadataRsp() + } + + simulator(LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH) { + return new LocalStorageKvmBackend.CleanupVmMetadataRsp() + } } } diff --git a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy index 0dbe59bfc01..09344d42a5c 100755 --- a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy @@ -522,6 +522,22 @@ class NfsPrimaryStorageSpec extends PrimaryStorageSpec { return rsp } + simulator(NfsPrimaryStorageKVMBackend.WRITE_VM_METADATA_PATH) { + return new NfsPrimaryStorageKVMBackendCommands.WriteVmMetadataRsp() + } + + simulator(NfsPrimaryStorageKVMBackend.GET_VM_INSTANCE_METADATA_PATH) { + return new NfsPrimaryStorageKVMBackendCommands.GetVmInstanceMetadataRsp() + } + + simulator(NfsPrimaryStorageKVMBackend.SCAN_VM_METADATA_PATH) { + return new NfsPrimaryStorageKVMBackendCommands.ScanVmMetadataRsp() + } + + simulator(NfsPrimaryStorageKVMBackend.CLEANUP_VM_METADATA_PATH) { + return new NfsPrimaryStorageKVMBackendCommands.CleanupVmMetadataRsp() + } + VFS.vfsHook(NfsPrimaryStorageKVMBackend.NFS_REBASE_VOLUME_BACKING_FILE_PATH, xspec) { rsp, HttpEntity e, EnvSpec spec -> def cmd = JSONObjectUtil.toObject(e.body, NfsPrimaryStorageKVMBackendCommands.NfsRebaseVolumeBackingFileCmd.class)