diff --git a/build/deploydb.sh b/build/deploydb.sh index 49964c58754..f8446f31450 100755 --- a/build/deploydb.sh +++ b/build/deploydb.sh @@ -42,6 +42,7 @@ mkdir -p ${flyway_sql} eval "rm -f ${flyway_sql}/*" cp ${base}/../conf/db/V0.6__schema.sql ${flyway_sql} cp ${base}/../conf/db/upgrade/* ${flyway_sql} +cp ${base}/../conf/db/zsv/* ${flyway_sql} if [[ ! -n $host ]] || [[ ! -n $port ]];then url="jdbc:mysql://localhost:3306/zstack" diff --git a/compute/src/main/java/org/zstack/compute/legacy/ComputeLegacyGlobalProperty.java b/compute/src/main/java/org/zstack/compute/legacy/ComputeLegacyGlobalProperty.java index c0f1c80a38d..be845c85c2b 100644 --- a/compute/src/main/java/org/zstack/compute/legacy/ComputeLegacyGlobalProperty.java +++ b/compute/src/main/java/org/zstack/compute/legacy/ComputeLegacyGlobalProperty.java @@ -10,4 +10,11 @@ public class ComputeLegacyGlobalProperty { */ @GlobalProperty(name="legacyCpuTopologyFix", defaultValue = "false") public static boolean cpuTopologyFix; + /** + * if enableNvRamTypeVolume = true, NvRam type volume will be created if UEFI is enabled; + * if enableNvRamTypeVolume = false, NvRam type volume will not be created, NvRam & TpmState will save in host + * (not in Primary storage); + */ + @GlobalProperty(name="enable.nv.ram.type.volume", defaultValue = "false") + public static boolean enableNvRamTypeVolume; } diff --git a/compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java b/compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java new file mode 100644 index 00000000000..3b480ffbce9 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/CleanupVmInstanceMetadataOnPrimaryStorageGC.java @@ -0,0 +1,106 @@ +package org.zstack.compute.vm; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.core.db.Q; +import org.zstack.core.gc.GC; +import org.zstack.core.gc.GCCompletion; +import org.zstack.core.gc.TimeBasedGarbageCollector; +import org.zstack.header.host.HostVO; +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.metadata.VmMetadataPathBuildExtensionPoint; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +public class CleanupVmInstanceMetadataOnPrimaryStorageGC extends TimeBasedGarbageCollector { + private static final CLogger logger = Utils.getLogger(CleanupVmInstanceMetadataOnPrimaryStorageGC.class); + + @Autowired + private PluginRegistry pluginRgty; + + @GC + public String primaryStorageUuid; + @GC + public String vmUuid; + @GC + public String rootVolumeUuid; + @GC + public String metadataPath; + @GC + public String hostUuid; + + public static String getGCName(String vmUuid) { + return String.format("gc-cleanup-vm-metadata-%s", vmUuid); + } + + @Override + protected void triggerNow(GCCompletion completion) { + if (!dbf.isExist(primaryStorageUuid, PrimaryStorageVO.class)) { + logger.debug(String.format("[MetadataCleanupGC] primary storage[uuid:%s] no longer exists, " + + "cancel gc for vm[uuid:%s]", primaryStorageUuid, vmUuid)); + completion.cancel(); + return; + } + + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + if (psType == null) { + logger.debug(String.format("[MetadataCleanupGC] primary storage[uuid:%s] type not found, " + + "cancel gc for vm[uuid:%s]", primaryStorageUuid, vmUuid)); + completion.cancel(); + return; + } + + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + boolean requireHost = ext != null && ext.requireHostForCleanup(); + + // Determine effective hostUuid based on whether the PS type requires a host for cleanup. + String effectiveHostUuid = hostUuid; + if (!requireHost) { + effectiveHostUuid = null; + } else { + if (effectiveHostUuid == null) { + logger.debug(String.format("[MetadataCleanupGC] hostUuid is null and ps[uuid:%s, type:%s] " + + "requires host for cleanup, cancel gc for vm[uuid:%s]", + primaryStorageUuid, psType, vmUuid)); + completion.cancel(); + return; + } + if (!dbf.isExist(effectiveHostUuid, HostVO.class)) { + logger.debug(String.format("[MetadataCleanupGC] host[uuid:%s] no longer exists " + + "and ps[uuid:%s, type:%s] requires host for cleanup, " + + "metadata is unreachable, cancel gc for vm[uuid:%s]", + effectiveHostUuid, primaryStorageUuid, psType, vmUuid)); + completion.cancel(); + return; + } + } + + CleanupVmInstanceMetadataOnPrimaryStorageMsg msg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); + msg.setPrimaryStorageUuid(primaryStorageUuid); + msg.setVmInstanceUuid(vmUuid); + msg.setRootVolumeUuid(rootVolumeUuid); + msg.setMetadataPath(metadataPath); + msg.setHostUuid(effectiveHostUuid); + + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, primaryStorageUuid); + bus.send(msg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.info(String.format("[MetadataCleanupGC] successfully cleaned up metadata " + + "for vm[uuid:%s] on ps[uuid:%s]", vmUuid, primaryStorageUuid)); + completion.success(); + } else { + logger.warn(String.format("[MetadataCleanupGC] failed to clean up metadata " + + "for vm[uuid:%s] on ps[uuid:%s]: %s", vmUuid, primaryStorageUuid, reply.getError())); + completion.fail(reply.getError()); + } + } + }); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java b/compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java index 485d78a1b79..55d5eb7a1c1 100644 --- a/compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java +++ b/compute/src/main/java/org/zstack/compute/vm/InstantiateVmFromNewCreatedStruct.java @@ -3,6 +3,7 @@ import org.zstack.header.configuration.VmCustomSpecificationStruct; import org.zstack.header.host.CpuArchitecture; import org.zstack.header.vm.*; +import org.zstack.header.vm.devices.VmDevicesSpec; import java.util.ArrayList; import java.util.List; @@ -32,6 +33,7 @@ public class InstantiateVmFromNewCreatedStruct { private List dataDisks; private List deprecatedDataVolumeSpecs = new ArrayList<>(); private VmCustomSpecificationStruct vmCustomSpecification; + private VmDevicesSpec devicesSpec; public List getCandidatePrimaryStorageUuidsForRootVolume() { return candidatePrimaryStorageUuidsForRootVolume; @@ -87,6 +89,14 @@ public void setVmCustomSpecification(VmCustomSpecificationStruct vmCustomSpecifi this.vmCustomSpecification = vmCustomSpecification; } + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } + public List getRootVolumeSystemTags() { return rootVolumeSystemTags; } @@ -167,6 +177,7 @@ public static InstantiateVmFromNewCreatedStruct fromMessage(InstantiateNewCreate struct.setDataDisks(msg.getDataDisks()); struct.setDeprecatedDataVolumeSpecs(msg.getDeprecatedDataVolumeSpecs()); struct.setVmCustomSpecification(msg.getVmCustomSpecification()); + struct.setDevicesSpec(msg.getDevicesSpec()); return struct; } @@ -187,6 +198,7 @@ public static InstantiateVmFromNewCreatedStruct fromMessage(CreateVmInstanceMsg struct.setDataDisks(msg.getDataDisks()); struct.setDeprecatedDataVolumeSpecs(msg.getDeprecatedDataVolumeSpecs()); struct.setVmCustomSpecification(msg.getVmCustomSpecification()); + struct.setDevicesSpec(msg.getDevicesSpec()); return struct; } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java index fe60c8ec1a1..250421d2edb 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java @@ -472,9 +472,9 @@ public void done(ErrorCodeList errorCodeList) { .flatMap(List::stream).map(VmCdRomInventory::getUuid) .collect(Collectors.toList()); dbf.removeByPrimaryKeys(cdRomUuids, VmCdRomVO.class); - dbf.removeByPrimaryKeys(vminvs.stream().map(p -> p.getInventory().getUuid()) - .collect(Collectors.toList()), - VmInstanceVO.class); + + List vmUuidList = transform(vminvs, p -> p.getInventory().getUuid()); + dbf.removeByPrimaryKeys(vmUuidList, VmInstanceVO.class); } completion.success(); diff --git a/compute/src/main/java/org/zstack/compute/vm/VmDeleteVolumeFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmDeleteVolumeFlow.java index b791ecb1658..5c29c8e8292 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmDeleteVolumeFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmDeleteVolumeFlow.java @@ -49,7 +49,9 @@ public void run(final FlowTrigger trigger, Map data) { final boolean templated = isTemplated(spec.getVmInventory().getUuid()); /* data volume must be detached anyway no matter if it is going to be deleted */ - if (spec.getVmInventory().getAllVolumes().size() > 1) { + boolean anyDataVolume = spec.getVmInventory().getAllVolumes().stream() + .anyMatch(arg -> VolumeType.Data.toString().equals(arg.getType())); + if (anyDataVolume) { detachDataVolumes(spec); } @@ -60,7 +62,11 @@ public void run(final FlowTrigger trigger, Map data) { return; } - List volumeTypes = Arrays.asList(VolumeType.Root.toString(), VolumeType.Memory.toString(), VolumeType.Cache.toString()); + List volumeTypes = Arrays.asList( + VolumeType.Root.toString(), + VolumeType.Memory.toString(), + VolumeType.Cache.toString() + ); List ctx = transformAndRemoveNull(spec.getVmInventory().getAllVolumes(), arg -> { if (VolumeType.Data.toString().equals(arg.getType()) && !deleteDataDisk && !templated) { return null; 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..8baf8b5ae86 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/VmExpungeMetadataFlow.java @@ -0,0 +1,143 @@ +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.VolumeInventory; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@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) { + if (!VmGlobalConfig.VM_METADATA_ENABLED.value(Boolean.class)) { + trigger.next(); + return; + } + + 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(); + + VolumeInventory rootVolume = spec.getVmInventory().getRootVolume(); + String psUuid = rootVolume != null ? rootVolume.getPrimaryStorageUuid() : null; + if (psUuid == null) { + logger.debug(String.format("[MetadataExpunge] vm[uuid:%s] root volume has no primaryStorageUuid, " + + "skipping metadata cleanup", vmUuid)); + trigger.next(); + return; + } + + + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, psUuid).findValue(); + if (psType == null) { + logger.warn(String.format("[MetadataExpunge] primary storage[uuid:%s] not found for vm[uuid:%s], " + + "skip metadata cleanup", psUuid, vmUuid)); + trigger.next(); + return; + } + + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + if (ext == null) { + logger.warn(String.format("[MetadataExpunge] no VmMetadataPathBuildExtensionPoint found for ps[uuid:%s, type:%s], " + + "skip metadata cleanup", psUuid, psType)); + trigger.next(); + return; + } + final String metadataPath; + try { + metadataPath = ext.buildVmMetadataPath(psUuid, vmUuid); + } catch (Exception e) { + logger.warn(String.format("[MetadataExpunge] failed to build metadata path for vm[uuid:%s] on ps[uuid:%s], " + + "skip metadata cleanup: %s", vmUuid, psUuid, e.getMessage())); + trigger.next(); + return; + } + + String hostUuid = null; + if (ext.requireHostForCleanup()) { + hostUuid = spec.getVmInventory().getHostUuid(); + if (hostUuid == null) { + hostUuid = spec.getVmInventory().getLastHostUuid(); + } + + if (hostUuid == null) { + logger.warn(String.format("[MetadataExpunge] vm[uuid:%s] hostUuid is null, " + + "ps[uuid:%s, type:%s] requires host for cleanup, skip without submitting GC", + vmUuid, psUuid, psType)); + trigger.next(); + return; + } + } + + String rootVolumeUuid = rootVolume.getUuid(); + CleanupVmInstanceMetadataOnPrimaryStorageMsg cmsg = new CleanupVmInstanceMetadataOnPrimaryStorageMsg(); + cmsg.setPrimaryStorageUuid(psUuid); + cmsg.setVmInstanceUuid(vmUuid); + cmsg.setMetadataPath(metadataPath); + cmsg.setRootVolumeUuid(rootVolumeUuid); + cmsg.setHostUuid(hostUuid); + final String finalPsUuid = psUuid; + final String finalHostUuid = hostUuid; + + bus.makeTargetServiceIdByResourceUuid(cmsg, PrimaryStorageConstant.SERVICE_ID, psUuid); + bus.send(cmsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.info(String.format("[MetadataExpunge] successfully deleted metadata for vm[uuid:%s] on ps[uuid:%s]", + vmUuid, finalPsUuid)); + } else { + logger.warn(String.format("[MetadataExpunge] failed to delete metadata for vm[uuid:%s] on ps[uuid:%s]: %s, " + + "submitting GC job for retry", vmUuid, finalPsUuid, reply.getError())); + submitGC(finalPsUuid, vmUuid, rootVolumeUuid, metadataPath, finalHostUuid); + } + trigger.next(); + } + }); + } + + private void submitGC(String psUuid, String vmUuid, String rootVolumeUuid, String metadataPath, String hostUuid) { + CleanupVmInstanceMetadataOnPrimaryStorageGC gc = new CleanupVmInstanceMetadataOnPrimaryStorageGC(); + gc.NAME = CleanupVmInstanceMetadataOnPrimaryStorageGC.getGCName(vmUuid); + gc.primaryStorageUuid = psUuid; + gc.vmUuid = vmUuid; + gc.rootVolumeUuid = rootVolumeUuid; + gc.metadataPath = metadataPath; + gc.hostUuid = hostUuid; + long gcIntervalSec = TimeUnit.HOURS.toSeconds(VmGlobalConfig.VM_METADATA_CLEANUP_GC_INTERVAL.value(Long.class)); + gc.deduplicateSubmit(gcIntervalSec, TimeUnit.SECONDS); + + logger.info(String.format("[MetadataExpunge] submitted GC job [%s] for vm[uuid:%s] on ps[uuid:%s]", gc.NAME, vmUuid, psUuid)); + } +} 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..fa72dcbe396 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmGlobalConfig.java @@ -113,6 +113,7 @@ public class VmGlobalConfig { @GlobalConfigDef(defaultValue = "false", type = Boolean.class, description = "sync clock after vm resume") public static GlobalConfig VM_CLOCK_SYNC_AFTER_VM_RESUME = new GlobalConfig(CATEGORY, "vm.clock.sync.after.vm.resume"); + @BindResourceConfig(value = {VmInstanceVO.class, ClusterVO.class}) @GlobalConfigValidation(validValues = {"true", "false"}) public static GlobalConfig ENABLE_UEFI_SECURE_BOOT = new GlobalConfig(CATEGORY, "enable.uefi.secure.boot"); @@ -133,4 +134,61 @@ public class VmGlobalConfig { @GlobalConfigValidation(validValues = {"None", "AuthenticAMD"}) @BindResourceConfig(value = {VmInstanceVO.class}) public static GlobalConfig VM_CPUID_VENDOR = new GlobalConfig(CATEGORY, "vm.cpuid.vendor"); + + @GlobalConfigDef(defaultValue = "true", type = Boolean.class, description = "whether reset TPM state after VM clone") + @GlobalConfigValidation(validValues = {"true", "false"}) + @BindResourceConfig(value = {VmInstanceVO.class, ClusterVO.class}) + public static GlobalConfig RESET_TPM_AFTER_VM_CLONE = new GlobalConfig(CATEGORY, "reset.tpm.after.vm.clone"); + + @GlobalConfigDef(defaultValue = "false", type = Boolean.class, description = "allowed TPM VM start without KMS") + @GlobalConfigValidation(validValues = {"true", "false"}) + public static GlobalConfig ALLOWED_TPM_VM_WITHOUT_KMS = new GlobalConfig(CATEGORY, "allowed.tpm.vm.without.kms"); + + @GlobalConfigValidation(validValues = {"true", "false"}) + public static GlobalConfig VM_METADATA_ENABLED = new GlobalConfig(CATEGORY, "vm.metadata.enabled"); + + @GlobalConfigValidation() + public static GlobalConfig VM_METADATA_LAST_REFRESH_VERSION = new GlobalConfig(CATEGORY, "vm.metadata.lastRefreshVersion"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 100) + public static GlobalConfig VM_METADATA_FLUSH_CONCURRENCY = new GlobalConfig(CATEGORY, "vm.metadata.flush.concurrency"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 300) + public static GlobalConfig VM_METADATA_FLUSH_POLL_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.flush.pollInterval"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 1000) + public static GlobalConfig VM_METADATA_FLUSH_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.flush.batchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 168) + public static GlobalConfig VM_METADATA_CLEANUP_GC_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.cleanup.gc.interval"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 10) + public static GlobalConfig VM_METADATA_FLUSH_MAX_RETRY = new GlobalConfig(CATEGORY, "vm.metadata.flush.maxRetry"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 120) + public static GlobalConfig VM_METADATA_FLUSH_ZOMBIE_CLAIM_THRESHOLD = new GlobalConfig(CATEGORY, "vm.metadata.flush.zombieClaimThreshold"); + + @GlobalConfigValidation(numberGreaterThan = 21599, numberLessThan = 172801) + public static GlobalConfig VM_METADATA_MAINTENANCE_CONTENT_DRIFT_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.contentDriftInterval"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 86400) + public static GlobalConfig VM_METADATA_MAINTENANCE_STALE_RECOVERY_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.staleRecoveryInterval"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 1000) + public static GlobalConfig VM_METADATA_MAINTENANCE_STALE_RECOVERY_MAX_CYCLES = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.staleRecoveryMaxCycles"); + + @GlobalConfigValidation(numberGreaterThan = 0) + public static GlobalConfig VM_METADATA_PAYLOAD_REJECT_THRESHOLD = new GlobalConfig(CATEGORY, "vm.metadata.payload.rejectThreshold"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 86400) + public static GlobalConfig VM_METADATA_MAINTENANCE_ORPHAN_CHECK_INTERVAL = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.orphanCheckInterval"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 20) + public static GlobalConfig VM_METADATA_MAINTENANCE_STALE_RECOVERY_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.staleRecoveryBatchSize"); + + @GlobalConfigValidation(numberGreaterThan = 9, numberLessThan = 201) + public static GlobalConfig VM_METADATA_MAINTENANCE_CONTENT_DRIFT_BATCH_SIZE = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.contentDriftBatchSize"); + + @GlobalConfigValidation(numberGreaterThan = 0, numberLessThan = 31) + public static GlobalConfig VM_METADATA_MAINTENANCE_CONTENT_DRIFT_BATCH_SLEEP_SEC = new GlobalConfig(CATEGORY, "vm.metadata.maintenance.contentDriftBatchSleepSec"); } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java index c17cf5d5179..57169124127 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java @@ -23,14 +23,22 @@ import org.zstack.header.network.l3.*; import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO; import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO_; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.storage.primary.PrimaryStorageVO_; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; import org.zstack.header.storage.snapshot.VolumeSnapshotVO_; import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.host.HostState; +import org.zstack.header.host.HostStatus; +import org.zstack.header.host.HostVO; +import org.zstack.header.host.HostVO_; import org.zstack.header.vm.*; import org.zstack.header.vm.cdrom.*; +import org.zstack.header.vm.APIRegisterVmInstanceFromMetadataMsg; import org.zstack.header.vm.devices.VmInstanceResourceMetadataGroupVO; import org.zstack.header.vm.devices.VmInstanceResourceMetadataGroupVO_; +import org.zstack.header.vm.metadata.VmMetadataPathBuildExtensionPoint; import org.zstack.header.volume.*; import org.zstack.network.l2.L2NetworkHostUtils; import org.zstack.resourceconfig.ResourceConfigFacade; @@ -166,6 +174,8 @@ else if (msg instanceof APIAttachVmNicToVmMsg) { validate((APIConvertTemplatedVmInstanceToVmInstanceMsg) msg); } else if (msg instanceof APIDeleteTemplatedVmInstanceMsg) { validate((APIDeleteTemplatedVmInstanceMsg) msg); + } else if (msg instanceof APIRegisterVmInstanceFromMetadataMsg) { + validate((APIRegisterVmInstanceFromMetadataMsg) msg); } if (msg instanceof NewVmInstanceMessage2) { @@ -1012,7 +1022,8 @@ private void validateRootDiskOffering(ImageMediaType imgFormat, APICreateVmInsta } if (msg.getRootDiskSize() <= 0) { - throw new ApiMessageInterceptionException(operr("Unexpected root disk settings")); + throw new ApiMessageInterceptionException(operr("Unexpected root disk settings") + .withException("DiskAO[0].size is mandatory when image format is ISO")); } } } @@ -1318,4 +1329,65 @@ private void validate(APIFstrimVmMsg msg) { } msg.setHostUuid(t.get(1, String.class)); } + + private void validate(APIRegisterVmInstanceFromMetadataMsg msg) { + String path = msg.getMetadataPath(); + if (StringUtils.isEmpty(path)) { + throw new ApiMessageInterceptionException(argerr("metadataPath cannot be empty or null")); + } + + // Delegate path validation to the storage-type-specific extension + String psUuid = msg.getPrimaryStorageUuid(); + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, psUuid).findValue(); + if (psType == null) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s] not found", psUuid)); + } + + VmMetadataPathBuildExtensionPoint ext = pluginRgty.getExtensionFromMap(psType, VmMetadataPathBuildExtensionPoint.class); + if (ext == null) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s, type:%s] does not support vm metadata", psUuid, psType)); + } + + String error = ext.validateMetadataPath(psUuid, path); + if (error != null) { + throw new ApiMessageInterceptionException(argerr("%s", error)); + } + + boolean psAttachedToCluster = Q.New(PrimaryStorageClusterRefVO.class) + .eq(PrimaryStorageClusterRefVO_.primaryStorageUuid, psUuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, msg.getClusterUuid()) + .isExists(); + if (!psAttachedToCluster) { + throw new ApiMessageInterceptionException(argerr( + "primary storage[uuid:%s] is not attached to cluster[uuid:%s]", + psUuid, msg.getClusterUuid())); + } + + if (msg.getHostUuid() != null) { + boolean hostAvailable = Q.New(HostVO.class) + .eq(HostVO_.uuid, msg.getHostUuid()) + .eq(HostVO_.clusterUuid, msg.getClusterUuid()) + .eq(HostVO_.state, HostState.Enabled) + .eq(HostVO_.status, HostStatus.Connected) + .isExists(); + if (!hostAvailable) { + throw new ApiMessageInterceptionException(argerr( + "host[uuid:%s] is not in cluster[uuid:%s] or not Enabled/Connected", msg.getHostUuid(), msg.getClusterUuid())); + } + } else { + boolean hasHost = Q.New(HostVO.class) + .eq(HostVO_.clusterUuid, msg.getClusterUuid()) + .eq(HostVO_.state, HostState.Enabled) + .eq(HostVO_.status, HostStatus.Connected) + .isExists(); + if (!hasHost) { + throw new ApiMessageInterceptionException(argerr( + "no available host found in cluster[uuid:%s], " + + "please specify hostUuid or ensure there is at least one connected host in the cluster", + msg.getClusterUuid())); + } + } + } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index f4a2eb2e14b..bdbe656f727 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -7,6 +7,8 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.transaction.annotation.Transactional; import org.zstack.compute.allocator.HostAllocatorManager; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.compute.vm.devices.VmTpmManager; import org.zstack.core.Platform; import org.zstack.core.asyncbatch.While; import org.zstack.core.cascade.CascadeConstant; @@ -45,6 +47,7 @@ import org.zstack.header.message.*; import org.zstack.header.network.l3.*; import org.zstack.header.storage.primary.*; +import org.zstack.header.tpm.TpmConstants; import org.zstack.header.vm.*; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicHostUuid; import org.zstack.header.vm.ChangeVmMetaDataMsg.AtomicVmState; @@ -56,6 +59,7 @@ import org.zstack.header.vm.VmInstanceSpec.CdRomSpec; import org.zstack.header.vm.VmInstanceSpec.HostName; import org.zstack.header.vm.VmInstanceSpec.IsoSpec; +import org.zstack.header.vm.additions.ResetVmTpmMsg; import org.zstack.header.vm.cdrom.*; import org.zstack.header.vm.devices.VmInstanceResourceMetadataManager; import org.zstack.header.vo.ResourceVO; @@ -140,6 +144,8 @@ public class VmInstanceBase extends AbstractVmInstance { private VmInstanceResourceMetadataManager vidm; @Autowired private NetworkServiceManager nwServiceMgr; + @Autowired + private TpmEncryptedResourceKeyBackend tpmKeyBackend; protected VmInstanceVO self; protected VmInstanceVO originalCopy; @@ -147,6 +153,19 @@ public class VmInstanceBase extends AbstractVmInstance { private final static StaticIpOperator ipOperator = new StaticIpOperator(); private final static VmConfigSyncHelper vmConfigSyncHelper = new VmConfigSyncHelper(); + private void detachTpmKeyProviderBestEffort(String tpmUuid) { + if (tpmUuid == null) { + return; + } + try { + tpmKeyBackend.detachKeyProviderFromTpm(tpmUuid); + } catch (Throwable t) { + logger.warn(String.format( + "failed to detach key provider from TPM[uuid:%s]: %s", + tpmUuid, t.getMessage()), t); + } + } + protected void checkState(final String hostUuid, final NoErrorCompletion completion) { CheckVmStateOnHypervisorMsg msg = new CheckVmStateOnHypervisorMsg(); msg.setVmInstanceUuids(list(self.getUuid())); @@ -1270,6 +1289,8 @@ public void handle(Map data) { CollectionUtils.safeForEach(pluginRgty.getExtensionList(VmAfterExpungeExtensionPoint.class), arg -> arg.vmAfterExpunge(inv)); + final String tpmUuidForEncryptedKeyRef = VmTpmManager.findTpmUuidForVmOrNull(self.getUuid()); + callVmJustBeforeDeleteFromDbExtensionPoint(); dbf.reload(self); @@ -1281,6 +1302,7 @@ public void handle(Map data) { if (inv.getRootVolumeUuid() != null) { dbf.eoCleanup(VolumeVO.class, inv.getRootVolumeUuid()); } + detachTpmKeyProviderBestEffort(tpmUuidForEncryptedKeyRef); completion.success(); } }).error(new FlowErrorHandler(completion) { @@ -2580,12 +2602,15 @@ public void success() { if (self.getState() != VmInstanceState.Destroyed) { changeVmStateInDb(VmInstanceStateEvent.destroyed); } + final String tpmUuidForEncryptedKeyRef = VmTpmManager.findTpmUuidForVmOrNull(self.getUuid()); callVmJustBeforeDeleteFromDbExtensionPoint(); dbf.removeCollection(self.getVmCdRoms(), VmCdRomVO.class); dbf.remove(getSelf()); dbf.eoCleanup(VmInstanceVO.class, self.getUuid()); + detachTpmKeyProviderBestEffort(tpmUuidForEncryptedKeyRef); } else if (deletionPolicy == VmInstanceDeletionPolicy.DBOnly || deletionPolicy == VmInstanceDeletionPolicy.KeepVolume) { String accountUuid = acntMgr.getOwnerAccountUuidOfResource(inv.getUuid()); + final String tpmUuidForEncryptedKeyRef = VmTpmManager.findTpmUuidForVmOrNull(self.getUuid()); new SQLBatch() { @Override protected void scripts() { @@ -2599,6 +2624,7 @@ protected void scripts() { sql(VmInstanceVO.class).eq(VmInstanceVO_.uuid, self.getUuid()).hardDelete(); } }.execute(); + detachTpmKeyProviderBestEffort(tpmUuidForEncryptedKeyRef); callVmJustAfterDeleteFromDbExtensionPoint(inv, accountUuid); } else if (deletionPolicy == VmInstanceDeletionPolicy.Delay) { changeVmStateInDb(VmInstanceStateEvent.destroyed); @@ -3561,6 +3587,40 @@ public void run(MessageReply reply) { } }); + flow(new NoRollbackFlow() { + String __name__ = "reset-vm-tpm"; + + @Override + public boolean skip(Map data) { + boolean resetTpm; + if (msg.getResetTpm() == null) { + resetTpm = rcf.getResourceConfigValue( + VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE, + msg.getVmInstanceUuid(), Boolean.class); + } else { + resetTpm = msg.getResetTpm(); + } + return !resetTpm; + } + + @Override + public void run(FlowTrigger trigger, Map map) { + ResetVmTpmMsg rmsg = new ResetVmTpmMsg(); + rmsg.setVmInstanceUuid(msg.getVmInstanceUuid()); + bus.makeLocalServiceId(rmsg, TpmConstants.SERVICE_ID); + bus.send(rmsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + trigger.fail(reply.getError()); + return; + } + trigger.next(); + } + }); + } + }); + flow(new NoRollbackFlow() { String __name__ = "update-vm-name"; @@ -7711,6 +7771,7 @@ private VmInstanceSpec buildVmInstanceSpecFromStruct(InstantiateVmFromNewCreated spec.setDataDisks(struct.getDataDisks()); spec.setDeprecatedDisksSpecs(struct.getDeprecatedDataVolumeSpecs()); spec.setVmCustomSpecification(struct.getVmCustomSpecification()); + spec.setDevicesSpec(struct.getDevicesSpec()); List cdRomSpecs = buildVmCdRomSpecsForNewCreated(spec); spec.setCdRomSpecs(cdRomSpecs); diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java index 0838c24d9c3..2af241834d7 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceManagerImpl.java @@ -895,7 +895,7 @@ private void handle(APICreateVmNicMsg msg) { FlowChain flowChain = FlowChainBuilder.newSimpleFlowChain(); flowChain.setName(String.format("create-nic-on-l3-network-%s", msg.getL3NetworkUuid())); flowChain.then(new NoRollbackFlow() { - String __name__ = "create-nic-and-presist-to-db"; + String __name__ = "create-nic-and-persist-to-db"; @Override public void run(FlowTrigger trigger, Map data) { @@ -1214,16 +1214,22 @@ public void setup() { flow(new Flow() { String __name__ = "call-after-persist-vm-extensions"; + List done = new ArrayList<>(); + @Override public void run(FlowTrigger trigger, Map data) { - pluginRgty.getExtensionList(VmInstanceCreateExtensionPoint.class).forEach( - extensionPoint -> extensionPoint.afterPersistVmInstanceVO(finalVo)); + for (VmInstanceCreateExtensionPoint extension : pluginRgty.getExtensionList(VmInstanceCreateExtensionPoint.class)) { + done.add(extension); + extension.afterPersistVmInstanceVO(finalVo, msg); + } trigger.next(); } @Override public void rollback(FlowRollback trigger, Map data) { - // do nothing + Collections.reverse(done); + CollectionUtils.safeForEach(done, + extension -> extension.afterRollbackPersistVmInstanceVO(finalVo, msg)); trigger.rollback(); } }); @@ -1315,7 +1321,7 @@ public void run(FlowTrigger trigger, Map data) { smsg.setRootDiskOfferingUuid(rootDisk.getDiskOfferingUuid()); } else if (rootDisk.getSize() > 0) { dvo = new DiskOfferingVO(); - dvo.setUuid(Platform.getUuid()); + dvo.setUuid(getUuid()); dvo.setAccountUuid(msg.getAccountUuid()); dvo.setDiskSize(rootDisk.getSize()); dvo.setName("for-create-vm-" + finalVo.getUuid()); @@ -1352,6 +1358,7 @@ public void run(FlowTrigger trigger, Map data) { smsg.setDataDisks(msg.getDataDisks()); smsg.setDeprecatedDataVolumeSpecs(msg.getDeprecatedDataVolumeSpecs()); smsg.setVmCustomSpecification(msg.getVmCustomSpecification()); + smsg.setDevicesSpec(msg.getDevicesSpec()); bus.makeTargetServiceIdByResourceUuid(smsg, VmInstanceConstant.SERVICE_ID, finalVo.getUuid()); bus.send(smsg, new CloudBusCallBack(smsg) { @Override @@ -1380,7 +1387,7 @@ public void rollback(FlowRollback chain, Map data) { } DestroyVmInstanceMsg dmsg = new DestroyVmInstanceMsg(); dmsg.setVmInstanceUuid(finalVo.getUuid()); - dmsg.setDeletionPolicy(VmInstanceDeletionPolicyManager.VmInstanceDeletionPolicy.Direct); + dmsg.setDeletionPolicy(VmInstanceDeletionPolicy.Direct); bus.makeTargetServiceIdByResourceUuid(dmsg, VmInstanceConstant.SERVICE_ID, finalVo.getUuid()); bus.send(dmsg, new CloudBusCallBack(null) { @Override diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java index 33afa043278..bebcae3520b 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceUtils.java @@ -44,6 +44,7 @@ public static CreateVmInstanceMsg fromAPICreateVmInstanceMsg(APICreateVmInstance cmsg.setGuestOsType(msg.getGuestOsType()); cmsg.setArchitecture(msg.getArchitecture()); cmsg.setStrategy(msg.getStrategy()); + cmsg.setDevicesSpec(msg.getDevicesSpec()); if (CollectionUtils.isNotEmpty(msg.getDataDiskOfferingUuids()) || CollectionUtils.isNotEmpty(msg.getDataDiskSizes())) { cmsg.setPrimaryStorageUuidForDataVolume(getPSUuidForDataVolume(msg.getSystemTags())); } 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/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java b/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java new file mode 100644 index 00000000000..0bd6a8eaaae --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyEncryptedResourceKeyManager.java @@ -0,0 +1,34 @@ +package org.zstack.compute.vm.devices; + +import org.zstack.header.errorcode.OperationFailureException; +import org.zstack.header.keyprovider.EncryptedResourceKeyManager; +import org.zstack.header.core.Completion; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import static org.zstack.core.Platform.operr; + +public class DummyEncryptedResourceKeyManager implements EncryptedResourceKeyManager { + private static final CLogger logger = Utils.getLogger(DummyEncryptedResourceKeyManager.class); + + @Override + public void getOrCreateKey(GetOrCreateResourceKeyContext ctx, + ReturnValueCompletion completion) { + logger.warn(String.format("crypto module not installed, cannot create resource key for %s[uuid:%s]", + ctx.getResourceType(), ctx.getResourceUuid())); + completion.fail(operr("crypto module is not installed, cannot manage resource encryption keys")); + } + + @Override + public ResourceKeyResult getKey(GetOrCreateResourceKeyContext ctx) { + logger.warn(String.format("crypto module not installed, cannot get resource key for %s[uuid:%s]", + ctx.getResourceType(), ctx.getResourceUuid())); + throw new OperationFailureException(operr("crypto module is not installed, cannot manage resource encryption keys")); + } + + @Override + public void rollbackCreatedKey(ResourceKeyResult result, Completion completion) { + completion.success(); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java b/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java new file mode 100644 index 00000000000..ebe04946506 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/DummyTpmEncryptedResourceKeyBackend.java @@ -0,0 +1,53 @@ +package org.zstack.compute.vm.devices; + +import org.zstack.header.core.Completion; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +public class DummyTpmEncryptedResourceKeyBackend implements TpmEncryptedResourceKeyBackend { + private static final CLogger logger = Utils.getLogger(DummyTpmEncryptedResourceKeyBackend.class); + + @Override + public void attachKeyProviderToTpm(String tpmUuid, String keyProviderUuid) { + logger.debug("ignore attach key provider to TPM request for TPM uuid " + tpmUuid + + " and key provider uuid " + keyProviderUuid); + } + + @Override + public void detachKeyProviderFromTpm(String tpmUuid) { + logger.debug("ignore detach key provider from TPM request for TPM uuid " + tpmUuid); + } + + @Override + public String findKeyProviderUuidByTpm(String tpmUuid) { + return null; + } + + @Override + public String findKeyProviderUuidByName(String providerName) { + return null; + } + + @Override + public String findKeyProviderNameByTpm(String tpmUuid) { + return null; + } + + @Override + public Integer findKeyVersionByTpm(String tpmUuid) { + return null; + } + + @Override + public String defaultKeyProviderUuid() { + return null; + } + + @Override + public void cloneEncryptedResourceKey(CloneEncryptedResourceKeyContext context, Completion completion) { + // do nothing + logger.debug("ignore clone encrypted resource key request for TPM uuid " + + context.srcTpmUuid + " -> " + context.dstTpmUuid); + completion.success(); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/TpmApiInterceptor.java b/compute/src/main/java/org/zstack/compute/vm/devices/TpmApiInterceptor.java new file mode 100644 index 00000000000..78724e72a57 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmApiInterceptor.java @@ -0,0 +1,112 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.Platform; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.db.Q; +import org.zstack.header.apimediator.ApiMessageInterceptionException; +import org.zstack.header.apimediator.ApiMessageInterceptor; +import org.zstack.header.apimediator.StopRoutingException; +import org.zstack.header.errorcode.SysErrors; +import org.zstack.header.message.APIMessage; +import org.zstack.header.tpm.api.*; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import static org.zstack.core.Platform.err; +import static org.zstack.header.tpm.TpmConstants.*; +import static org.zstack.header.tpm.TpmErrors.*; +import static org.zstack.header.vm.VmInstanceConstant.KVM_HYPERVISOR_TYPE; + +public class TpmApiInterceptor implements ApiMessageInterceptor { + private static final CLogger logger = Utils.getLogger(TpmApiInterceptor.class); + + @Autowired + private CloudBus bus; + + @Override + public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException { + if (msg instanceof APIGetTpmCapabilityMsg) { + validate((APIGetTpmCapabilityMsg) msg); + } else if (msg instanceof APIAddTpmMsg) { + validate((APIAddTpmMsg) msg); + } else if (msg instanceof APIRemoveTpmMsg) { + validate((APIRemoveTpmMsg) msg); + } else if (msg instanceof APIUpdateTpmMsg) { + validate((APIUpdateTpmMsg) msg); + } + + return msg; + } + + private void validate(APIGetTpmCapabilityMsg msg) { + makeSureVmInstanceIsKvmType(msg.getVmInstanceUuid()); + } + + private void validate(APIAddTpmMsg msg) { + makeSureVmInstanceIsKvmType(msg.getVmInstanceUuid()); + + boolean tpmExists = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, msg.getVmInstanceUuid()) + .isExists(); + if (tpmExists) { + throw new ApiMessageInterceptionException(err(TPM_ALREADY_EXISTS, "tpm device already exists")); + } + + boolean vmInSupportState = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, msg.getVmInstanceUuid()) + .in(VmInstanceVO_.state, SUPPORT_VM_STATES_FOR_TPM_OPERATION) + .isExists(); + if (!vmInSupportState) { + throw new ApiMessageInterceptionException(err(VM_STATE_ERROR, + "The current VM state does not support adding TPM operations") + .withOpaque("support.vm.state", SUPPORT_VM_STATES_FOR_TPM_OPERATION)); + } + + if (msg.getResourceUuid() == null) { + msg.setResourceUuid(Platform.getUuid()); + } + } + + private void validate(APIRemoveTpmMsg msg) { + makeSureVmInstanceIsKvmType(msg.getVmInstanceUuid()); + + boolean tpmExists = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, msg.getVmInstanceUuid()) + .isExists(); + if (!tpmExists) { + APIRemoveTpmEvent evt = new APIRemoveTpmEvent(msg.getId()); + bus.publish(evt); + throw new StopRoutingException(); + } + + boolean vmInSupportState = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, msg.getVmInstanceUuid()) + .in(VmInstanceVO_.state, SUPPORT_VM_STATES_FOR_TPM_OPERATION) + .isExists(); + if (!vmInSupportState) { + throw new ApiMessageInterceptionException(err(VM_STATE_ERROR, + "The current VM state does not support removing TPM operations") + .withOpaque("support.vm.state", SUPPORT_VM_STATES_FOR_TPM_OPERATION)); + } + } + + private void validate(APIUpdateTpmMsg msg) { + makeSureVmInstanceIsKvmType(msg.getVmInstanceUuid()); + bus.makeTargetServiceIdByResourceUuid(msg, SERVICE_ID, msg.getTpmUuid()); + } + + private void makeSureVmInstanceIsKvmType(String vmInstanceUuid) { + String hypervisorType = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hypervisorType) + .eq(VmInstanceVO_.uuid, vmInstanceUuid) + .findValue(); + if (!KVM_HYPERVISOR_TYPE.equals(hypervisorType)) { + throw new ApiMessageInterceptionException(err(SysErrors.NOT_SUPPORTED, "only allowed for kvm type VM instance")); + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java b/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java new file mode 100644 index 00000000000..4189641756d --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmEncryptedResourceKeyBackend.java @@ -0,0 +1,69 @@ +package org.zstack.compute.vm.devices; + +import org.zstack.header.core.Completion; + +/** + * Responsible for handling the replication or reset of encryption resource keys + * and other tasks in VM TPM cloning scenarios. + */ +public interface TpmEncryptedResourceKeyBackend { + + /** + * Build relationship from {@link org.zstack.header.tpm.entity.TpmVO} to EncryptedResourceKeyRefVO + * Non-async call. + */ + void attachKeyProviderToTpm(String tpmUuid, String keyProviderUuid); + + /** + * Clean relationship from {@link org.zstack.header.tpm.entity.TpmVO} to EncryptedResourceKeyRefVO + * Non-async call. + */ + void detachKeyProviderFromTpm(String tpmUuid); + + /** + * maybe null (when crypto module is not installed) + */ + String findKeyProviderUuidByTpm(String tpmUuid); + + /** + * maybe null (when crypto module is not installed) + */ + String findKeyProviderUuidByName(String providerName); + + /** + * maybe null (when crypto module is not installed) + */ + String findKeyProviderNameByTpm(String tpmUuid); + + /** + * maybe null (when crypto module is not installed) + */ + Integer findKeyVersionByTpm(String tpmUuid); + + /** + * maybe null (when crypto module is not installed) + */ + String defaultKeyProviderUuid(); + + static class CloneEncryptedResourceKeyContext { + public String srcTpmUuid; + public String dstTpmUuid; + + /** + * Whether to reset (regenerate) the key on the target TPM. + *
    + *
  • {@code true}:Regenerate the key for the target TPM + * without inheriting the encrypted data from the source TPM.
  • + *
  • {@code false}:Copy the existing keys from the source TPM + * to the target TPM to ensure they remain consistent.
  • + *
+ */ + public boolean resetTpm; + } + + /** + * In a VM cloning scenario, copy or reset the encryption resource key + * from the source TPM to the target TPM. + */ + void cloneEncryptedResourceKey(CloneEncryptedResourceKeyContext context, Completion completion); +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/TpmMessageAutoCompleter.java b/compute/src/main/java/org/zstack/compute/vm/devices/TpmMessageAutoCompleter.java new file mode 100644 index 00000000000..a7024551cc2 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/TpmMessageAutoCompleter.java @@ -0,0 +1,76 @@ +package org.zstack.compute.vm.devices; + +import org.zstack.core.db.Q; +import org.zstack.header.apimediator.ApiMessageInterceptionException; +import org.zstack.header.apimediator.GlobalApiMessageInterceptor; +import org.zstack.header.message.APIDeleteMessage; +import org.zstack.header.message.APIMessage; +import org.zstack.header.tpm.api.APIGetTpmCapabilityMsg; +import org.zstack.header.tpm.api.APIRemoveTpmMsg; +import org.zstack.header.tpm.api.APIUpdateTpmMsg; +import org.zstack.header.tpm.api.TpmMessage; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; + +import java.util.List; + +import static org.zstack.core.Platform.argerr; +import static org.zstack.core.Platform.err; +import static org.zstack.header.tpm.TpmErrors.TPM_NOT_FOUND; +import static org.zstack.utils.CollectionDSL.list; + +public class TpmMessageAutoCompleter implements GlobalApiMessageInterceptor { + @Override + public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException { + if (msg instanceof TpmMessage) { + validateAndComplete((TpmMessage) msg); + } + return msg; + } + + @Override + @SuppressWarnings("rawtypes") + public List getMessageClassToIntercept() { + return list(APIGetTpmCapabilityMsg.class, APIRemoveTpmMsg.class, APIUpdateTpmMsg.class); + } + + @Override + public InterceptorPosition getPosition() { + return InterceptorPosition.FRONT; + } + + private void validateAndComplete(TpmMessage msg) throws ApiMessageInterceptionException { + String tpmUuid = msg.getTpmUuid(); + String vmUuid = msg.getVmInstanceUuid(); + + if (tpmUuid == null && vmUuid == null) { + throw new ApiMessageInterceptionException(argerr("tpmUuid and vmInstanceUuid cannot be null at the same time")); + } + + if (tpmUuid != null && vmUuid != null) { + boolean exists = Q.New(TpmVO.class) + .eq(TpmVO_.uuid, tpmUuid) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .isExists(); + if (!exists) { + throw new ApiMessageInterceptionException(argerr("tpmUuid[%s] and vmInstanceUuid[%s] are not consistent", tpmUuid, vmUuid)); + } + } else if (vmUuid != null) { + tpmUuid = Q.New(TpmVO.class) + .select(TpmVO_.uuid) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .findValue(); + if (tpmUuid == null && (!(msg instanceof APIDeleteMessage))) { + throw new ApiMessageInterceptionException(err(TPM_NOT_FOUND, "tpm for vm[%s] does not exist", vmUuid)); + } else { + msg.setTpmUuid(tpmUuid); + } + } else { + vmUuid = Q.New(TpmVO.class) + .select(TpmVO_.vmInstanceUuid) + .eq(TpmVO_.uuid, tpmUuid) + .findValue(); + msg.setVmInstanceUuid(vmUuid); + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmExtensions.java b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmExtensions.java new file mode 100644 index 00000000000..fe0535f27c6 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmExtensions.java @@ -0,0 +1,132 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.BuildVmSpecExtensionPoint; +import org.zstack.compute.vm.VmSystemTags; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQLBatch; +import org.zstack.header.tpm.entity.TpmSpec; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.CreateVmInstanceMsg; +import org.zstack.header.vm.VmInstanceCreateExtensionPoint; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmMachineType; +import org.zstack.header.vm.devices.NvRamSpec; +import org.zstack.header.vm.devices.VmDevicesSpec; +import org.zstack.resourceconfig.ResourceConfig; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; + +public class VmTpmExtensions implements VmInstanceCreateExtensionPoint, + BuildVmSpecExtensionPoint { + private static final CLogger logger = Utils.getLogger(VmTpmExtensions.class); + + @Autowired + private VmTpmManager vmTpmManager; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private TpmEncryptedResourceKeyBackend resourceKeyBackend; + + @Override + public void preCreateVmInstance(CreateVmInstanceMsg msg) { + // do-nothing + } + + @Override + public void afterPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) { + final VmDevicesSpec spec = msg.getDevicesSpec(); + if (spec == null || spec.getTpm() == null || !spec.getTpm().isEnable()) { + return; + } + + new SQLBatch() { + @Override + protected void scripts() { + final TpmVO tpm = vmTpmManager.persistTpmVO(null, vo.getUuid()); + final String keyProviderUuid = spec.getTpm().getKeyProviderUuid(); + if (keyProviderUuid != null) { + resourceKeyBackend.attachKeyProviderToTpm(tpm.getUuid(), keyProviderUuid); + } + } + }.execute(); + } + + @Override + public void afterRollbackPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) { + String tpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vo.getUuid()) + .select(TpmVO_.uuid) + .findValue(); + if (tpmUuid == null) { + return; + } + + new SQLBatch() { + @Override + protected void scripts() { + try { + resourceKeyBackend.detachKeyProviderFromTpm(tpmUuid); + } finally { + vmTpmManager.deleteTpmVO(tpmUuid); + } + } + }.execute(); + } + + @Override + public void afterBuildVmSpec(VmInstanceSpec spec) { + String vmUuid = spec.getVmInventory().getUuid(); + + String tpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .select(TpmVO_.uuid) + .findValue(); + boolean needRegisterNvRam = tpmUuid != null; + if (!needRegisterNvRam) { + String bootMode = VmSystemTags.BOOT_MODE.getTokenByResourceUuid(vmUuid, VmSystemTags.BOOT_MODE_TOKEN); + if (vmTpmManager.isUefiBootMode(bootMode)) { + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(ENABLE_UEFI_SECURE_BOOT.getIdentity()); + needRegisterNvRam = resourceConfig.getResourceConfigValue(vmUuid, Boolean.class) == Boolean.TRUE; + } + } + + VmDevicesSpec devicesSpec = spec.getDevicesSpec(); + if (devicesSpec == null) { + devicesSpec = new VmDevicesSpec(); + spec.setDevicesSpec(devicesSpec); + } + + if (needRegisterNvRam) { + NvRamSpec nvRamSpec = devicesSpec.getNvRam(); + if (nvRamSpec == null) { + nvRamSpec = new NvRamSpec(); + devicesSpec.setNvRam(nvRamSpec); + } + + nvRamSpec.setNeedRegister(true); + } + + if (tpmUuid != null) { + TpmSpec tpmSpec = devicesSpec.getTpm(); + if (tpmSpec == null) { + tpmSpec = new TpmSpec(); + devicesSpec.setTpm(tpmSpec); + } + + tpmSpec.setEnable(true); + tpmSpec.setTpmUuid(tpmUuid); + } + + if (needRegisterNvRam && spec.getOsSpec().getMachineType() == null) { + spec.getOsSpec().setMachineType(VmMachineType.q35.toString()); + logger.debug(String.format( + "auto-set machineType to q35 for VM[uuid:%s] because NvRam/TPM registration is needed", vmUuid)); + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java new file mode 100644 index 00000000000..b486fe09379 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmManager.java @@ -0,0 +1,92 @@ +package org.zstack.compute.vm.devices; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmSystemTags; +import org.zstack.core.Platform; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.header.image.ImageBootMode; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.resourceconfig.ResourceConfig; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static org.zstack.compute.vm.VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT; +import static org.zstack.header.vm.additions.VmHostFileType.NvRam; +import static org.zstack.header.vm.additions.VmHostFileType.TpmState; +import static org.zstack.utils.CollectionDSL.list; + +public class VmTpmManager { + private static final CLogger logger = Utils.getLogger(VmTpmManager.class); + + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + + public TpmVO persistTpmVO(String tpmUuid, String vmUuid) { + if (tpmUuid == null) { + tpmUuid = Platform.getUuid(); + } + TpmVO tpm = new TpmVO(); + tpm.setUuid(tpmUuid); + tpm.setResourceName("TPM-for-VM-" + vmUuid); + tpm.setVmInstanceUuid(vmUuid); + databaseFacade.persistAndRefresh(tpm); + + logger.debug("Persisted TpmVO for VM " + vmUuid + " with uuid=" + tpm.getUuid()); + return tpm; + } + + public void deleteTpmVO(String tpmUuid) { + databaseFacade.removeByPrimaryKey(tpmUuid, TpmVO.class); + } + + public static String findTpmUuidForVmOrNull(String vmInstanceUuid) { + return Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vmInstanceUuid) + .select(TpmVO_.uuid) + .findValue(); + } + + /** + * @param bootMode boot mode, null is Legacy + */ + public static boolean isUefiBootMode(String bootMode) { + return Objects.equals(bootMode, ImageBootMode.UEFI.toString()) + || Objects.equals(bootMode, ImageBootMode.UEFI_WITH_CSM.toString()); + } + + public boolean needRegisterNvRam(String vmUuid) { + return needRegister(NvRam, vmUuid); + } + + public Set vmHostFileTypeNeedRegisterForVm(String vmUuid) { + String bootMode = VmSystemTags.BOOT_MODE.getTokenByResourceUuid(vmUuid, VmSystemTags.BOOT_MODE_TOKEN); + if (!isUefiBootMode(bootMode)) { + return Collections.emptySet(); + } + + boolean hasTpm = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .isExists(); + if (hasTpm) { + return new HashSet<>(list(NvRam, TpmState)); + } + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(ENABLE_UEFI_SECURE_BOOT.getIdentity()); + return resourceConfig.getResourceConfigValue(vmUuid, Boolean.class) == Boolean.TRUE ? + new HashSet<>(list(NvRam)) : Collections.emptySet(); + } + + public boolean needRegister(VmHostFileType type, String vmUuid) { + return vmHostFileTypeNeedRegisterForVm(vmUuid).contains(type); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmRekeyAssociation.java b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmRekeyAssociation.java new file mode 100644 index 00000000000..ae469a0a9b4 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/devices/VmTpmRekeyAssociation.java @@ -0,0 +1,35 @@ +package org.zstack.compute.vm.devices; + +import org.zstack.core.db.Q; +import org.zstack.header.keyprovider.KeyProviderRekeyAssociationExtensionPoint; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.utils.CollectionUtils; + +import java.util.Collections; +import java.util.List; + +public class VmTpmRekeyAssociation implements KeyProviderRekeyAssociationExtensionPoint { + @Override + public String getType() { + return VmInstanceVO.class.getSimpleName(); + } + + @Override + public String getAssociatedResourceType() { + return TpmVO.class.getSimpleName(); + } + + @Override + public List getAssociatedResourceUuids(List resourceUuids) { + if (CollectionUtils.isEmpty(resourceUuids)) { + return Collections.emptyList(); + } + + return Q.New(TpmVO.class) + .in(TpmVO_.vmInstanceUuid, resourceUuids) + .select(TpmVO_.uuid) + .listValues(); + } +} diff --git a/conf/db/zsv/V5.0.0__schema.sql b/conf/db/zsv/V5.0.0__schema.sql new file mode 100644 index 00000000000..ecbb6f112ac --- /dev/null +++ b/conf/db/zsv/V5.0.0__schema.sql @@ -0,0 +1,159 @@ +-- Feature: vTPM & Secure Boot | ZSPHER-1, ZSPHER-14 + +CREATE TABLE IF NOT EXISTS `zstack`.`TpmVO` ( + `uuid` char(32) NOT NULL UNIQUE, + `vmInstanceUuid` char(32) NOT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + CONSTRAINT `fkTpmVOVmInstanceVO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON UPDATE RESTRICT ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`VmHostFileVO` ( + `uuid` char(32) NOT NULL UNIQUE, + `vmInstanceUuid` char(32) NOT NULL, + `hostUuid` char(32) NOT NULL, + `type` varchar(64) NOT NULL COMMENT 'NvRam, TpmState', + `path` varchar(1024) NOT NULL COMMENT 'Absolute path of the file on the host', + `lastSyncReason` varchar(255) DEFAULT NULL COMMENT 'The reason for the last sync operation', + `changeDate` timestamp NULL DEFAULT NULL COMMENT 'Timestamp when file was reported changed, null after sync', + `lastSyncDate` timestamp NULL DEFAULT NULL COMMENT 'Timestamp of the last successful sync', + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + INDEX `idxVmHostFileVOVmInstanceUuid` (`vmInstanceUuid`), + INDEX `idxVmHostFileVOHostUuid` (`hostUuid`), + UNIQUE KEY `ukVmHostFileVO` (`vmInstanceUuid`, `hostUuid`, `type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`VmHostBackupFileVO` ( + `uuid` char(32) NOT NULL UNIQUE, + `resourceUuid` char(32) NOT NULL, + `type` varchar(64) NOT NULL COMMENT 'NvRam, TpmState', + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + UNIQUE KEY `ukVmHostBackupFileVO` (`resourceUuid`, `type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`VmHostFileContentVO` ( + `uuid` char(32) NOT NULL UNIQUE COMMENT 'VmHostFileVO.uuid or VmHostBackupFileVO.uuid', + `content` MEDIUMBLOB DEFAULT NULL, + `format` varchar(64) NOT NULL COMMENT 'Raw, TarballGzip', + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + CONSTRAINT `fkVmHostFileContentVOResourceVO` FOREIGN KEY (`uuid`) REFERENCES `ResourceVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Feature: KMS | ZSPHER-46, ZSPHER-60, ZSPHER-61, ZSPHER-62 + +CREATE TABLE IF NOT EXISTS `zstack`.`KeyProviderVO` ( + `uuid` varchar(32) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL, + `description` varchar(2048) DEFAULT NULL, + `type` varchar(32) NOT NULL, + `connected` boolean NOT NULL DEFAULT FALSE, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + UNIQUE KEY `ukKeyProviderVOName` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`KmsVO` ( + `uuid` varchar(32) NOT NULL UNIQUE, + `endpoint` varchar(255) NOT NULL, + `port` int unsigned NOT NULL, + `kmipVersion` varchar(32) DEFAULT NULL, + `username` varchar(255) DEFAULT NULL, + `password` varchar(255) DEFAULT NULL, + `trustState` varchar(32) NOT NULL DEFAULT 'MUTUAL_UNTRUSTED', + `activeIdentityUuid` varchar(32) DEFAULT NULL, + `serverCertExpiredDate` timestamp NULL DEFAULT NULL, + `serverCertPem` text DEFAULT NULL, + PRIMARY KEY (`uuid`), + INDEX `idxKmsVOActiveIdentityUuid` (`activeIdentityUuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`KmsIdentityVO` ( + `uuid` varchar(32) NOT NULL UNIQUE, + `kmsUuid` varchar(32) NOT NULL, + `identityType` varchar(32) NOT NULL, + `clientCertPem` text DEFAULT NULL, + `clientKeyPem` text DEFAULT NULL, + `csrPem` text DEFAULT NULL, + `certExpiredDate` timestamp NULL DEFAULT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`uuid`), + UNIQUE KEY `ukKmsIdentityVOKmsUuidType` (`kmsUuid`, `identityType`), + INDEX `idxKmsIdentityVOKmsUuid` (`kmsUuid`), + CONSTRAINT `fkKmsIdentityVOKmsVO` FOREIGN KEY (`kmsUuid`) REFERENCES `KmsVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`NkpVO` ( + `uuid` varchar(32) NOT NULL UNIQUE, + `kdf` varchar(64) NOT NULL, + `saltPolicy` varchar(64) NOT NULL, + `backedUp` boolean NOT NULL DEFAULT FALSE, + `currentVersion` int unsigned DEFAULT NULL, + PRIMARY KEY (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`HostKeyIdentityVO` ( + `hostUuid` varchar(32) NOT NULL UNIQUE, + `publicKey` text NOT NULL, + `fingerprint` varchar(128) NOT NULL, + `verified` boolean NOT NULL DEFAULT FALSE, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`hostUuid`), + CONSTRAINT `fkHostKeyIdentityVOHostEO` FOREIGN KEY (`hostUuid`) REFERENCES `HostEO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `zstack`.`EncryptedResourceKeyRefVO` ( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `resourceType` varchar(255) NOT NULL, + `resourceUuid` varchar(32) NOT NULL, + `providerUuid` varchar(32) DEFAULT NULL, + `providerName` varchar(255) NOT NULL, + `keyVersion` int unsigned DEFAULT NULL, + `kekRef` varchar(255) DEFAULT NULL, + `wrappedDek` text NOT NULL, + `algorithm` varchar(64) DEFAULT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`id`), + INDEX `idxEncryptedResourceKeyRefVOResource` (`resourceType`, `resourceUuid`), + INDEX `idxEncryptedResourceKeyRefVOProviderUuid` (`providerUuid`), + INDEX `idxEncryptedResourceKeyRefVOProviderName` (`providerName`), + CONSTRAINT `fkEncryptedResourceKeyRefVOProviderUuid` FOREIGN KEY (`providerUuid`) REFERENCES `KeyProviderVO` (`uuid`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +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 NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`vmInstanceUuid`), + INDEX `idx_VmMetadataDirtyVO_unclaimed` (`managementNodeUuid`, `nextRetryTime`, `lastOpDate`), + 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`.`VmMetadataFlushStateVO` ( + `vmInstanceUuid` VARCHAR(32) NOT NULL, + `metadataSnapshot` LONGTEXT, + `lastFlushFinishTime` TIMESTAMP NULL DEFAULT NULL, + `pendingStaleRecovery` TINYINT(1) NOT NULL DEFAULT 0, + `staleRecoveryCount` INT NOT NULL DEFAULT 0, + `lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59', + PRIMARY KEY (`vmInstanceUuid`), + CONSTRAINT `fkVmMetadataFlushStateVOVmInstanceEO` FOREIGN KEY (`vmInstanceUuid`) REFERENCES `VmInstanceEO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/conf/db/zsv/4.1.0 b/conf/db/zsv_ref/4.1.0 similarity index 100% rename from conf/db/zsv/4.1.0 rename to conf/db/zsv_ref/4.1.0 diff --git a/conf/db/zsv/4.1.6 b/conf/db/zsv_ref/4.1.6 similarity index 100% rename from conf/db/zsv/4.1.6 rename to conf/db/zsv_ref/4.1.6 diff --git a/conf/db/zsv/4.2.0 b/conf/db/zsv_ref/4.2.0 similarity index 100% rename from conf/db/zsv/4.2.0 rename to conf/db/zsv_ref/4.2.0 diff --git a/conf/db/zsv/4.2.6 b/conf/db/zsv_ref/4.2.6 similarity index 100% rename from conf/db/zsv/4.2.6 rename to conf/db/zsv_ref/4.2.6 diff --git a/conf/db/zsv/4.2.8 b/conf/db/zsv_ref/4.2.8 similarity index 100% rename from conf/db/zsv/4.2.8 rename to conf/db/zsv_ref/4.2.8 diff --git a/conf/db/zsv/4.3.0 b/conf/db/zsv_ref/4.3.0 similarity index 100% rename from conf/db/zsv/4.3.0 rename to conf/db/zsv_ref/4.3.0 diff --git a/conf/db/zsv/4.3.1 b/conf/db/zsv_ref/4.3.1 similarity index 100% rename from conf/db/zsv/4.3.1 rename to conf/db/zsv_ref/4.3.1 diff --git a/conf/deploydb.sh b/conf/deploydb.sh index a9f8dd90673..0a9bdc2a659 100755 --- a/conf/deploydb.sh +++ b/conf/deploydb.sh @@ -42,6 +42,7 @@ mkdir -p $flyway_sql cp $base/db/V0.6__schema.sql $flyway_sql cp $base/db/upgrade/* $flyway_sql +cp $base/db/zsv/* $flyway_sql url="jdbc:mysql://$host:$port/zstack" diff --git a/conf/errorCodes/iam1.xml b/conf/errorCodes/iam1.xml deleted file mode 100644 index 83920b66ec0..00000000000 --- a/conf/errorCodes/iam1.xml +++ /dev/null @@ -1,18 +0,0 @@ - - IAM1 - - - 1000 - IAM1 general error - - - - 2001 - Move account group to a wrong place - - - - 3001 - The specific resource is not a resource ensemble member - - diff --git a/conf/errorCodes/sso.xml b/conf/errorCodes/sso.xml deleted file mode 100644 index 844265d8c58..00000000000 --- a/conf/errorCodes/sso.xml +++ /dev/null @@ -1,12 +0,0 @@ - - SSO - - 1001 - invalid SSO token - - - - 2001 - SSO account not found - - diff --git a/conf/errorCodes/sys.xml b/conf/errorCodes/sys.xml index c5b763f86ba..b484cbe5c1d 100755 --- a/conf/errorCodes/sys.xml +++ b/conf/errorCodes/sys.xml @@ -105,5 +105,10 @@ 1090 Multiple reasons + + + 1091 + Operation not supported + diff --git a/conf/errorCodes/tpm.xml b/conf/errorCodes/tpm.xml new file mode 100644 index 00000000000..e93b159fa71 --- /dev/null +++ b/conf/errorCodes/tpm.xml @@ -0,0 +1,24 @@ + + TPM + + + 1000 + General error + + + + 1701 + TPM already exists in this VM + + + + 1702 + TPM not found + + + + 1703 + The current VM state does not support this TPM operations + + + diff --git a/conf/globalConfig/vm.xml b/conf/globalConfig/vm.xml index 8563169b335..900d91a7749 100755 --- a/conf/globalConfig/vm.xml +++ b/conf/globalConfig/vm.xml @@ -289,7 +289,7 @@ enable.uefi.secure.boot enable uefi secure boot - true + false vm java.lang.Boolean @@ -317,4 +317,133 @@ java.lang.Boolean false + + + vm + vm.metadata.enabled + enable vm metadata + java.lang.Boolean + false + + + + vm + vm.metadata.lastRefreshVersion + Last completed upgrade refresh version, prevents duplicate triggers across MNs. Internal use only + java.lang.String + 5.0.0 + + + + vm + vm.metadata.flush.concurrency + Max concurrent metadata flush tasks per management node + java.lang.Integer + 10 + + + + vm + vm.metadata.flush.pollInterval + Metadata dirty poller interval in seconds + java.lang.Long + 5 + + + + vm + vm.metadata.flush.batchSize + Max dirty rows claimed per poll cycle + java.lang.Integer + 20 + + + + vm + vm.metadata.cleanup.gc.interval + Metadata cleanup GC retry interval in hours + java.lang.Long + 8 + + + + vm + vm.metadata.flush.maxRetry + Max flush retry count before marking VM metadata as stale + java.lang.Integer + 5 + + + + vm + vm.metadata.flush.zombieClaimThreshold + Minutes before an uncompleted flush claim is considered zombie and released + java.lang.Long + 15 + + + + vm + vm.metadata.maintenance.contentDriftInterval + Content drift detection interval in seconds. Drift detection is a fallback safety net; the primary mechanism is dirty marking via interceptors. + java.lang.Long + 86400 + + + + vm + vm.metadata.maintenance.staleRecoveryInterval + Stale VM metadata recovery interval in seconds + java.lang.Long + 600 + + + + vm + vm.metadata.maintenance.staleRecoveryMaxCycles + Max stale recovery cycles before entering permanent-stale state + java.lang.Integer + 10 + + + + vm + vm.metadata.payload.rejectThreshold + Max allowed VM metadata payload size in bytes. VMs exceeding this are skipped. + java.lang.Long + 33554432 + + + + vm + vm.metadata.maintenance.orphanCheckInterval + Orphan dirty-row detector interval in seconds. Releases rows claimed by dead management nodes. + java.lang.Long + 3600 + + + + vm + vm.metadata.maintenance.staleRecoveryBatchSize + Max stale VMs processed per recovery cycle + java.lang.Integer + 10 + + + + vm + vm.metadata.maintenance.contentDriftBatchSize + Number of VMs checked per batch during content drift detection + java.lang.Integer + 10 + + + + vm + vm.metadata.maintenance.contentDriftBatchSleepSec + Sleep time in seconds between content drift detection batches to limit DB pressure + java.lang.Long + 1 + + diff --git a/conf/persistence.xml b/conf/persistence.xml index aa295bcb365..e0b9cccdb32 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -18,12 +18,17 @@ org.zstack.resourceconfig.ResourceConfigVO org.zstack.header.managementnode.ManagementNodeVO org.zstack.header.managementnode.ManagementNodeContextVO + org.zstack.header.tpm.entity.TpmVO + org.zstack.header.vm.additions.VmHostFileVO + org.zstack.header.vm.additions.VmHostBackupFileVO + org.zstack.header.vm.additions.VmHostFileContentVO org.zstack.header.zone.ZoneVO org.zstack.header.zone.ZoneEO org.zstack.header.cluster.ClusterVO org.zstack.header.cluster.ClusterEO org.zstack.header.host.HostVO org.zstack.header.host.HostEO + org.zstack.header.host.HostKeyIdentityVO org.zstack.header.host.CpuFeaturesHistoryVO org.zstack.header.storage.primary.PrimaryStorageVO org.zstack.header.storage.primary.PrimaryStorageEO @@ -182,6 +187,11 @@ org.zstack.header.vm.VmInstanceNumaNodeVO org.zstack.header.host.HostNumaNodeVO org.zstack.header.core.encrypt.EncryptionIntegrityVO + org.zstack.header.keyprovider.KeyProviderVO + org.zstack.header.keyprovider.KmsVO + org.zstack.header.keyprovider.KmsIdentityVO + org.zstack.header.keyprovider.NkpVO + org.zstack.header.keyprovider.EncryptedResourceKeyRefVO org.zstack.storage.primary.sharedblock.SharedBlockCapacityVO org.zstack.header.vm.devices.VmInstanceResourceMetadataVO org.zstack.header.vm.devices.VmInstanceResourceMetadataArchiveVO @@ -211,5 +221,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.VmMetadataFlushStateVO diff --git a/conf/serviceConfig/primaryStorage.xml b/conf/serviceConfig/primaryStorage.xml index 337ce4eaac3..dcac7c151c0 100755 --- a/conf/serviceConfig/primaryStorage.xml +++ b/conf/serviceConfig/primaryStorage.xml @@ -81,7 +81,20 @@ org.zstack.header.storage.primary.APICleanUpStorageTrashOnPrimaryStorageMsg + org.zstack.header.storage.primary.APIAddStorageProtocolMsg + + + org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyMsg + + + + org.zstack.header.storage.primary.APITakeoverPrimaryStorageMsg + + + + org.zstack.header.storage.primary.APIScanVmInstanceMetadataFromPrimaryStorageMsg + diff --git a/conf/serviceConfig/tpm.xml b/conf/serviceConfig/tpm.xml new file mode 100644 index 00000000000..dab0467a8f6 --- /dev/null +++ b/conf/serviceConfig/tpm.xml @@ -0,0 +1,22 @@ + + + tpm + TpmApiInterceptor + + + org.zstack.header.tpm.api.APIAddTpmMsg + + + org.zstack.header.tpm.api.APIGetTpmCapabilityMsg + + + org.zstack.header.tpm.api.APIQueryTpmMsg + query + + + org.zstack.header.tpm.api.APIRemoveTpmMsg + + + org.zstack.header.tpm.api.APIUpdateTpmMsg + + diff --git a/conf/serviceConfig/vmInstance.xml b/conf/serviceConfig/vmInstance.xml index ab73fb6bea9..730951e0ece 100755 --- a/conf/serviceConfig/vmInstance.xml +++ b/conf/serviceConfig/vmInstance.xml @@ -264,4 +264,16 @@ org.zstack.header.vm.APIDeleteTemplatedVmInstanceMsg + + org.zstack.header.vm.APICleanupVmInstanceMetadataMsg + + + org.zstack.header.vm.APIRegisterVmInstanceFromMetadataMsg + + + org.zstack.header.vm.APIUpdateVmInstanceMetadataMsg + + + org.zstack.header.vm.APIGetVmInstanceMetadataFromPrimaryStorageMsg + diff --git a/conf/springConfigXml/Kvm.xml b/conf/springConfigXml/Kvm.xml index 7536337cff7..48f9956300c 100755 --- a/conf/springConfigXml/Kvm.xml +++ b/conf/springConfigXml/Kvm.xml @@ -165,9 +165,10 @@ - + + @@ -243,4 +244,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/springConfigXml/VmInstanceManager.xml b/conf/springConfigXml/VmInstanceManager.xml index 20e094378aa..1b284284d29 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 @@ -267,4 +268,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/org/zstack/core/log/LogSafeGson.java b/core/src/main/java/org/zstack/core/log/LogSafeGson.java index c3dcd26626c..8f0a1197821 100644 --- a/core/src/main/java/org/zstack/core/log/LogSafeGson.java +++ b/core/src/main/java/org/zstack/core/log/LogSafeGson.java @@ -99,6 +99,10 @@ String getMaskedValue(String raw) { return "*****"; } else if (annotation.type().tag()) { return tagInfoHider.call(raw); + } else if (annotation.type().longText()) { + if (raw == null) + return null; + return (raw.length() < 20) ? raw : raw.substring(0, 10) + "..." + raw.substring(raw.length() - 10); } else { return Utils.getLogMaskWords().getOrDefault(raw, uriPattern.matcher(raw).replaceFirst(":*****@")); } diff --git a/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java b/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java index 683f2580154..a13a7cd6742 100755 --- a/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java +++ b/core/src/main/java/org/zstack/core/workflow/SimpleFlowChain.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; import static org.zstack.core.Platform.inerr; @@ -260,9 +261,11 @@ public SimpleFlowChain error(FlowErrorHandler handler) { @SuppressWarnings("rawtypes") public SimpleFlowChain error(Consumer handler) { - AsyncBackup firstAsyncBackup = this.asyncBackups.isEmpty() ? null : this.asyncBackups.get(0); - AsyncBackup[] otherAsyncBackups = this.asyncBackups.isEmpty() ? new AsyncBackup[0] : - this.asyncBackups.subList(1, this.asyncBackups.size()).toArray(new AsyncBackup[0]); + DebugUtils.Assert(!asyncBackups.isEmpty(), + "propagateExceptionTo() must be called before error(Consumer)." + + " Call .propagateExceptionTo(completion) or .propagateExceptionTo((AsyncBackup) null) if you don't need async backup"); + AsyncBackup firstAsyncBackup = this.asyncBackups.get(0); + AsyncBackup[] otherAsyncBackups = this.asyncBackups.subList(1, this.asyncBackups.size()).toArray(new AsyncBackup[0]); DebugUtils.Assert(handler != null, "handler of errorHandler should not be null"); return error(new FlowErrorHandler(firstAsyncBackup, otherAsyncBackups) { @@ -320,12 +323,16 @@ public Map getData() { public SimpleFlowChain propagateExceptionTo(AsyncBackup... backups) { DebugUtils.Assert(backups != null, "backups in methods propagateExceptionTo() must be not null"); - DebugUtils.Assert(Arrays.stream(backups).noneMatch(Objects::isNull), - "backups in propagateExceptionTo() should not contain null elements"); DebugUtils.Assert(doneHandler == null, "propagateExceptionTo() must be called before SimpleFlowChain.done()"); DebugUtils.Assert(errorHandler == null, "propagateExceptionTo() must be called before SimpleFlowChain.error()"); DebugUtils.Assert(finallyHandler == null, "propagateExceptionTo() must be called before SimpleFlowChain.Finally()"); + this.asyncBackups.addAll(Arrays.asList(backups)); + this.asyncBackups.removeIf(Objects::isNull); + + if (this.asyncBackups.isEmpty()) { + this.asyncBackups.add(null); + } return this; } @@ -338,9 +345,11 @@ public SimpleFlowChain done(FlowDoneHandler handler) { @SuppressWarnings("rawtypes") public SimpleFlowChain done(Runnable runnable) { - AsyncBackup firstAsyncBackup = this.asyncBackups.isEmpty() ? null : this.asyncBackups.get(0); - AsyncBackup[] otherAsyncBackups = this.asyncBackups.isEmpty() ? new AsyncBackup[0] : - this.asyncBackups.subList(1, this.asyncBackups.size()).toArray(new AsyncBackup[0]); + DebugUtils.Assert(!asyncBackups.isEmpty(), + "propagateExceptionTo() must be called before done(Runnable)." + + " Call .propagateExceptionTo(completion) or .propagateExceptionTo((AsyncBackup) null) if you don't need async backup"); + AsyncBackup firstAsyncBackup = this.asyncBackups.get(0); + AsyncBackup[] otherAsyncBackups = this.asyncBackups.subList(1, this.asyncBackups.size()).toArray(new AsyncBackup[0]); DebugUtils.Assert(runnable != null, "runnable of doneHandler should not be null"); return done(new FlowDoneHandler(firstAsyncBackup, otherAsyncBackups) { diff --git a/header/src/main/java/org/zstack/header/errorcode/SysErrors.java b/header/src/main/java/org/zstack/header/errorcode/SysErrors.java index c59fba9c5b5..79e27fa69ac 100755 --- a/header/src/main/java/org/zstack/header/errorcode/SysErrors.java +++ b/header/src/main/java/org/zstack/header/errorcode/SysErrors.java @@ -26,6 +26,7 @@ public enum SysErrors { // ZSphere only MULTIPLE_REASONS(1090), + NOT_SUPPORTED(1091), ; private String code; diff --git a/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO.java b/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO.java new file mode 100644 index 00000000000..1433e075954 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO.java @@ -0,0 +1,89 @@ +package org.zstack.header.host; + +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; + +import javax.persistence.*; +import java.sql.Timestamp; + +@Entity +@Table +@EntityGraph( + parents = { + @EntityGraph.Neighbour(type = HostVO.class, myField = "hostUuid", targetField = "uuid") + } +) +public class HostKeyIdentityVO { + @Id + @Column + @ForeignKey(parentEntityClass = HostEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String hostUuid; + + @Column + private String publicKey; + + @Column + private String fingerprint; + + @Column + private Boolean verified = false; + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; + + @PreUpdate + private void preUpdate() { + lastOpDate = null; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getFingerprint() { + return fingerprint; + } + + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + + public Boolean getVerified() { + return verified; + } + + public void setVerified(Boolean verified) { + this.verified = verified; + } + + 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/host/HostKeyIdentityVO_.java b/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO_.java new file mode 100644 index 00000000000..a2bcfa7b850 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/HostKeyIdentityVO_.java @@ -0,0 +1,15 @@ +package org.zstack.header.host; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(HostKeyIdentityVO.class) +public class HostKeyIdentityVO_ { + public static volatile SingularAttribute hostUuid; + public static volatile SingularAttribute publicKey; + public static volatile SingularAttribute fingerprint; + public static volatile SingularAttribute verified; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/identity/rbac/RBACDescription.java b/header/src/main/java/org/zstack/header/identity/rbac/RBACDescription.java index 02d7b038c92..5b379e19fd7 100755 --- a/header/src/main/java/org/zstack/header/identity/rbac/RBACDescription.java +++ b/header/src/main/java/org/zstack/header/identity/rbac/RBACDescription.java @@ -37,6 +37,29 @@ default RBAC.AttributeSupportResourceBuilder attributeSupportResourceBuilder() { return new RBAC.AttributeSupportResourceBuilder(); } + /** + * If you want to contribute a resource to a resource ensemble, you can use this method: + * + * Ex: (Make TpmVO as a child resource of VmInstanceVO) + *
+     * resourceEnsembleContributorBuilder()
+     *     .resource(TpmVO.class)
+     *     .contributeTo(VmInstanceVO.class)
+     *     .build();
+     * 
+ * + * You must set @EntityGraph.Neighbour on VmInstanceVO.class + *
+     * \@EntityGraph(
+     *         friends = {
+     *                 \@EntityGraph.Neighbour(type = TpmVO.class, myField = "uuid", targetField = "vmInstanceUuid"),
+     *         }
+     * )
+     * 
+ * + * or use {@link org.zstack.header.identity.rbac.RBAC.ResourceEnsembleContributorBuilder#resourceWithCustomizeFindingMethods(java.lang.Class, java.util.function.Consumer, java.util.function.Consumer)} + * to specify how to find the resource by SQL. + */ default RBAC.ResourceEnsembleContributorBuilder resourceEnsembleContributorBuilder() { return new RBAC.ResourceEnsembleContributorBuilder(); } diff --git a/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java b/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java new file mode 100644 index 00000000000..ffa38675402 --- /dev/null +++ b/header/src/main/java/org/zstack/header/keyprovider/EncryptedResourceKeyManager.java @@ -0,0 +1,182 @@ +package org.zstack.header.keyprovider; + +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.core.Completion; + +/** + * Unified resource key management service. + * Business layers (TPM, volume encryption, etc.) call this to create/retrieve DEKs + * without knowing gRPC/key-tools protocol details. + */ +public interface EncryptedResourceKeyManager { + + /** + * Get or create a resource encryption key. + *

+ * Semantically reuses the existing key record for the same {@code (resourceType, resourceUuid)} + * when one is already available; otherwise creates a new one and returns the plaintext DEK. + *

+ * This contract does not guarantee concurrent linearizability by itself. Callers must not assume + * the interface alone provides serialization, uniqueness enforcement, or transaction-level protection + * across concurrent create requests. + * + * @param ctx context describing the resource and key provider + * @param completion returns {@link ResourceKeyResult} containing the plaintext DEK (base64) + */ + void getOrCreateKey(GetOrCreateResourceKeyContext ctx, + ReturnValueCompletion completion); + + /** + * Load the existing resource encryption key material only. + *

+ * Requires an {@code EncryptedResourceKeyRef} row and a usable secret reference already stored + * for the resource. Does not insert a ref row and does not call + * key-tool/KMS create APIs. + *

+ * The implementation may still call key-tool/KMS get/unwrap for the existing + * secret ref in order to return the plaintext DEK (for example defining the secret on the destination + * host during hot migration). That RPC is read-side materialization, not secret creation. + *

+ * On success, the implementation may update {@code EncryptedResourceKeyRef} provider columns to match + * the resolved key provider when they have drifted (same behavior as the existing-key branch of + * {@link #getOrCreateKey}). + * + * @param ctx same fields as {@link #getOrCreateKey}; identifies resource and provider + * @return {@link ResourceKeyResult} with {@code createdNewKey == false} on success + * @throws org.zstack.header.errorcode.OperationFailureException when the key cannot be loaded + */ + ResourceKeyResult getKey(GetOrCreateResourceKeyContext ctx); + + /** + * Roll back a newly created resource key during upper-layer workflow rollback. + *

+ * When {@link ResourceKeyResult#isCreatedNewKey()} is true, the implementation deletes the + * key-tool secret if one was materialized, then removes the {@code EncryptedResourceKeyRef} row + * for the resource (same storage effect as detaching the key provider from the resource). + * When {@code createdNewKey} is false (existing secret was reused), this is a no-op. + */ + void rollbackCreatedKey(ResourceKeyResult result, Completion completion); + + class GetOrCreateResourceKeyContext { + private String resourceUuid; + private String resourceType; + private String keyProviderUuid; + private String keyProviderName; + private String purpose; + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getKeyProviderName() { + return keyProviderName; + } + + public void setKeyProviderName(String keyProviderName) { + this.keyProviderName = keyProviderName; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + } + + class ResourceKeyResult { + private String resourceUuid; + private String resourceType; + private String keyProviderUuid; + private String keyProviderName; + private Integer keyVersion; + private String dekBase64; + private String secretRef; + private boolean createdNewKey; + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getKeyProviderName() { + return keyProviderName; + } + + public void setKeyProviderName(String keyProviderName) { + this.keyProviderName = keyProviderName; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getDekBase64() { + return dekBase64; + } + + public void setDekBase64(String dekBase64) { + this.dekBase64 = dekBase64; + } + + public String getSecretRef() { + return secretRef; + } + + public void setSecretRef(String secretRef) { + this.secretRef = secretRef; + } + + public boolean isCreatedNewKey() { + return createdNewKey; + } + + public void setCreatedNewKey(boolean createdNewKey) { + this.createdNewKey = createdNewKey; + } + } +} diff --git a/header/src/main/java/org/zstack/header/keyprovider/KeyProviderRekeyAssociationExtensionPoint.java b/header/src/main/java/org/zstack/header/keyprovider/KeyProviderRekeyAssociationExtensionPoint.java new file mode 100644 index 00000000000..e23f704b259 --- /dev/null +++ b/header/src/main/java/org/zstack/header/keyprovider/KeyProviderRekeyAssociationExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.keyprovider; + +import java.util.List; + +public interface KeyProviderRekeyAssociationExtensionPoint { + String getType(); + + String getAssociatedResourceType(); + + List getAssociatedResourceUuids(List resourceUuids); +} diff --git a/header/src/main/java/org/zstack/header/log/NoLogging.java b/header/src/main/java/org/zstack/header/log/NoLogging.java index b53c90f6333..5454acc849d 100644 --- a/header/src/main/java/org/zstack/header/log/NoLogging.java +++ b/header/src/main/java/org/zstack/header/log/NoLogging.java @@ -26,7 +26,9 @@ public boolean mask() { enum Type { Simple, Tag, - Uri; + Uri, + LongText, + ; public boolean simple() { return this == Simple; @@ -39,6 +41,10 @@ public boolean tag() { public boolean uri() { return this == Uri; } + + public boolean longText() { + return this == LongText; + } } Behavior behavior() default Behavior.Mask; diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java new file mode 100644 index 00000000000..d3258f249fd --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDefineMsg.java @@ -0,0 +1,87 @@ +package org.zstack.header.secret; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.log.NoLogging; +import org.zstack.header.message.NeedReplyMessage; + +/** + * Request to ensure secret on KVM host (for VM e.g. vTPM). + * Caller provides plaintext DEK (dekBase64), then host seals it with host public key + * and forwards the envelope to key-agent. + */ +public class SecretHostDefineMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + @NoLogging + private String dekBase64; + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + private String secretUuid; + private String description; + + @Override + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getDekBase64() { + return dekBase64; + } + + public void setDekBase64(String dekBase64) { + this.dekBase64 = dekBase64; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDefineReply.java b/header/src/main/java/org/zstack/header/secret/SecretHostDefineReply.java new file mode 100644 index 00000000000..13ef13c07df --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDefineReply.java @@ -0,0 +1,19 @@ +package org.zstack.header.secret; + +import org.zstack.header.message.MessageReply; + +/** Reply for SecretHostDefineMsg (define secret on host for VM e.g. vTPM). */ +public class SecretHostDefineReply extends MessageReply { + public static final String ERROR_CODE_KEYS_NOT_ON_DISK = "KEY_AGENT_KEYS_NOT_ON_DISK"; + public static final String ERROR_CODE_KEY_FILES_INTEGRITY_MISMATCH = "KEY_AGENT_KEY_FILES_INTEGRITY_MISMATCH"; + + private String secretUuid; + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java new file mode 100644 index 00000000000..f9502d16c67 --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteMsg.java @@ -0,0 +1,53 @@ +package org.zstack.header.secret; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; + +public class SecretHostDeleteMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + + @Override + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } +} diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostDeleteReply.java b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteReply.java new file mode 100644 index 00000000000..6640b5b60ca --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostDeleteReply.java @@ -0,0 +1,7 @@ +package org.zstack.header.secret; + +import org.zstack.header.message.MessageReply; + +/** Reply for SecretHostDeleteMsg. */ +public class SecretHostDeleteReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java b/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java new file mode 100644 index 00000000000..f63284880e0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostGetMsg.java @@ -0,0 +1,56 @@ +package org.zstack.header.secret; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; + +/** + * Request to get an existing secret on KVM host by vmUuid, purpose and keyVersion. + */ +public class SecretHostGetMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + + @Override + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } +} diff --git a/header/src/main/java/org/zstack/header/secret/SecretHostGetReply.java b/header/src/main/java/org/zstack/header/secret/SecretHostGetReply.java new file mode 100644 index 00000000000..cd474433110 --- /dev/null +++ b/header/src/main/java/org/zstack/header/secret/SecretHostGetReply.java @@ -0,0 +1,18 @@ +package org.zstack.header.secret; + +import org.zstack.header.message.MessageReply; + +/** Reply for SecretHostGetMsg. */ +public class SecretHostGetReply extends MessageReply { + public static final String ERROR_CODE_SECRET_NOT_FOUND = "KEY_AGENT_SECRET_NOT_FOUND"; + + private String secretUuid; + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java new file mode 100644 index 00000000000..e80bd4f9efc --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsg.java @@ -0,0 +1,35 @@ +package org.zstack.header.storage.primary; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APISyncCallMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +@RestRequest( + path = "/primary-storage/{uuid}/consistency", + responseClass = APICheckPrimaryStorageConsistencyReply.class, + method = HttpMethod.GET +) +public class APICheckPrimaryStorageConsistencyMsg extends APISyncCallMessage implements PrimaryStorageMessage { + @APIParam(resourceType = PrimaryStorageVO.class) + private String uuid; + + @Override + public String getPrimaryStorageUuid() { + return uuid; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public static APICheckPrimaryStorageConsistencyMsg __example__() { + APICheckPrimaryStorageConsistencyMsg msg = new APICheckPrimaryStorageConsistencyMsg(); + msg.setUuid(uuid(PrimaryStorageVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..e8a409b9718 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyReply + +doc { + title "CheckPrimaryStorageConsistency" + + category "storage.primary" + + desc """检查存储一致性""" + + rest { + request { + url "GET /v1/primary-storage/{uuid}/consistency" + + header (Authorization: 'OAuth the-session-uuid') + + clz APICheckPrimaryStorageConsistencyMsg.class + + desc """检查指定主存储的一致性状态""" + + params { + + column { + name "uuid" + enclosedIn "" + desc "主存储的UUID" + location "url" + 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 APICheckPrimaryStorageConsistencyReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java new file mode 100644 index 00000000000..41500468236 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReply.java @@ -0,0 +1,42 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.APIReply; +import org.zstack.header.rest.RestResponse; + +@RestResponse(fieldsTo = {"all"}) +public class APICheckPrimaryStorageConsistencyReply extends APIReply { + private boolean consistent; + private ConsistencyCheckStatus status; + private String candidateVgUuid; + + public boolean isConsistent() { + return consistent; + } + + public void setConsistent(boolean consistent) { + this.consistent = consistent; + } + + public ConsistencyCheckStatus getStatus() { + return status; + } + + public void setStatus(ConsistencyCheckStatus status) { + this.status = status; + } + + public String getCandidateVgUuid() { + return candidateVgUuid; + } + + public void setCandidateVgUuid(String candidateVgUuid) { + this.candidateVgUuid = candidateVgUuid; + } + + public static APICheckPrimaryStorageConsistencyReply __example__() { + APICheckPrimaryStorageConsistencyReply reply = new APICheckPrimaryStorageConsistencyReply(); + reply.setConsistent(true); + reply.setStatus(ConsistencyCheckStatus.CONSISTENT); + return reply; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..38ff803ebec --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APICheckPrimaryStorageConsistencyReplyDoc_zh_cn.groovy @@ -0,0 +1,41 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "检查存储一致性返回" + + field { + name "consistent" + desc "是否一致" + type "boolean" + since "5.0.0" + } + field { + name "status" + desc "一致性检查结果: CONSISTENT(VG 存在且 UUID 一致)/ UUID_MISMATCH(VG 存在但 UUID 不一致,可执行接管)/ VG_NOT_FOUND(未找到 WWID 匹配的 VG)" + type "ConsistencyCheckStatus" + since "5.0.0" + } + field { + name "candidateVgUuid" + desc "status 为 UUID_MISMATCH 时,存储上实际找到的 VG UUID(即接管候选);其他情况为 null" + type "String" + since "5.0.0" + } + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.storage.primary.APICheckPrimaryStorageConsistencyReply.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/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..af40d1d7ad7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,37 @@ +package org.zstack.header.storage.primary; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.APISyncCallMessage; +import org.zstack.header.message.DefaultTimeout; +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..182687a89cf --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReply.java @@ -0,0 +1,25 @@ +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 == null ? new ArrayList<>() : vmInstanceMetadata; + } + + public static APIScanVmInstanceMetadataFromPrimaryStorageReply __example__() { + APIScanVmInstanceMetadataFromPrimaryStorageReply evt = new APIScanVmInstanceMetadataFromPrimaryStorageReply(); + return evt; + } +} 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..3db2650faff --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APIScanVmInstanceMetadataFromPrimaryStorageReplyDoc_zh_cn.groovy @@ -0,0 +1,31 @@ +package org.zstack.header.storage.primary + +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/APITakeoverPrimaryStorageEvent.java b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java new file mode 100644 index 00000000000..47b76fc5e21 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEvent.java @@ -0,0 +1,60 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +import java.util.Collections; + +@RestResponse(fieldsTo = {"all"}) +public class APITakeoverPrimaryStorageEvent extends APIEvent { + private PrimaryStorageInventory inventory; + + private ReconnectResult reconnectResult; + + private String reconnectError; + + public APITakeoverPrimaryStorageEvent() { + } + + public APITakeoverPrimaryStorageEvent(String apiId) { + super(apiId); + } + + public PrimaryStorageInventory getInventory() { + return inventory; + } + + public void setInventory(PrimaryStorageInventory inventory) { + this.inventory = inventory; + } + + public ReconnectResult getReconnectResult() { + return reconnectResult; + } + + public void setReconnectResult(ReconnectResult reconnectResult) { + this.reconnectResult = reconnectResult; + } + + public String getReconnectError() { + return reconnectError; + } + + public void setReconnectError(String reconnectError) { + this.reconnectError = reconnectError; + } + + public static APITakeoverPrimaryStorageEvent __example__() { + APITakeoverPrimaryStorageEvent event = new APITakeoverPrimaryStorageEvent(); + + PrimaryStorageInventory ps = new PrimaryStorageInventory(); + ps.setName("PS1"); + ps.setUrl("/zstack_ps"); + ps.setType("SharedBlock"); + ps.setAttachedClusterUuids(Collections.singletonList(uuid())); + + event.setInventory(ps); + event.setReconnectResult(ReconnectResult.SUCCESS); + return event; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..a40aad2cd59 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageEventDoc_zh_cn.groovy @@ -0,0 +1,44 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.storage.primary.PrimaryStorageInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "接管主存储返回" + + ref { + name "inventory" + path "org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent.inventory" + desc "主存储信息" + type "PrimaryStorageInventory" + since "5.0.0" + clz PrimaryStorageInventory.class + } + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } + field { + name "reconnectResult" + desc "接管后重连结果,取值参见 ReconnectResult 枚举: SUCCESS(重连成功)/ FAILED(重连失败,但接管已完成且不可逆)/ NOT_ATTEMPTED(未尝试重连)" + type "ReconnectResult" + since "5.0.0" + } + field { + name "reconnectError" + desc "重连失败时的错误信息" + type "String" + since "5.0.0" + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java new file mode 100644 index 00000000000..4fbc9e5e5c3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsg.java @@ -0,0 +1,40 @@ +package org.zstack.header.storage.primary; + +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 java.util.concurrent.TimeUnit; + +@RestRequest( + path = "/primary-storage/{uuid}/takeover", + responseClass = APITakeoverPrimaryStorageEvent.class, + method = HttpMethod.PUT, + isAction = true +) +@DefaultTimeout(timeunit = TimeUnit.HOURS, value = 1) +public class APITakeoverPrimaryStorageMsg extends APIMessage implements PrimaryStorageMessage { + @APIParam(resourceType = PrimaryStorageVO.class) + private String uuid; + + @Override + public String getPrimaryStorageUuid() { + return uuid; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public static APITakeoverPrimaryStorageMsg __example__() { + APITakeoverPrimaryStorageMsg msg = new APITakeoverPrimaryStorageMsg(); + msg.setUuid(uuid(PrimaryStorageVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..802d0902dc6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/APITakeoverPrimaryStorageMsgDoc_zh_cn.groovy @@ -0,0 +1,61 @@ +package org.zstack.header.storage.primary + +import org.zstack.header.storage.primary.APITakeoverPrimaryStorageEvent + +doc { + title "TakeoverPrimaryStorage" + + category "storage.primary" + + desc """接管主存储。将其他ZStack平台的共享块主存储接管到当前平台。 +前置条件:主存储必须通过APICheckPrimaryStorageConsistencyMsg检查且consistent=false。 +接管操作不可逆(agent侧会执行VG rename、PV UUID reset、sanlock lockspace reset)。 +接管完成后自动触发reconnect,通过返回的reconnectResult/reconnectError获取重连状态。""" + + rest { + request { + url "PUT /v1/primary-storage/{uuid}/takeover" + + header (Authorization: 'OAuth the-session-uuid') + + clz APITakeoverPrimaryStorageMsg.class + + desc """接管指定主存储""" + + params { + + column { + name "uuid" + enclosedIn "takeoverPrimaryStorage" + desc "主存储的UUID" + location "url" + type "String" + 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 APITakeoverPrimaryStorageEvent.class + } + } +} \ No newline at end of file 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..4e508032775 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/CleanupVmInstanceMetadataOnPrimaryStorageMsg.java @@ -0,0 +1,52 @@ +package org.zstack.header.storage.primary; + +import org.zstack.header.message.NeedReplyMessage; + +public class CleanupVmInstanceMetadataOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage { + private String primaryStorageUuid; + private String vmInstanceUuid; + private String rootVolumeUuid; + private String metadataPath; + private String hostUuid; + + @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 getMetadataPath() { + return metadataPath; + } + + public void setMetadataPath(String metadataPath) { + this.metadataPath = metadataPath; + } + + 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/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/ConsistencyCheckStatus.java b/header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckStatus.java new file mode 100644 index 00000000000..d09ca702a90 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ConsistencyCheckStatus.java @@ -0,0 +1,10 @@ +package org.zstack.header.storage.primary; + +public enum ConsistencyCheckStatus { + /** VG found by WWID match, UUID matches the database — fully consistent */ + CONSISTENT, + /** VG found by WWID match, but its UUID differs from the database — takeover candidate */ + UUID_MISMATCH, + /** Hosts returned complete VG data, but no VG has a matching WWID set */ + VG_NOT_FOUND +} 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/ReconnectResult.java b/header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java new file mode 100644 index 00000000000..b4d7bb5241b --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/ReconnectResult.java @@ -0,0 +1,7 @@ +package org.zstack.header.storage.primary; + +public enum ReconnectResult { + SUCCESS, + FAILED, + NOT_ATTEMPTED +} 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..59ecf8a9ce5 --- /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 == null ? new ArrayList<>() : new ArrayList<>(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/primary/VmMetadataScanEntryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy new file mode 100644 index 00000000000..4a2e0fdf060 --- /dev/null +++ b/header/src/main/java/org/zstack/header/storage/primary/VmMetadataScanEntryDoc_zh_cn.groovy @@ -0,0 +1,67 @@ +package org.zstack.header.storage.primary + +doc { + + title "虚拟机元数据扫描数据" + + field { + name "vmUuid" + desc "" + type "String" + since "5.0.0" + } + field { + name "vmName" + desc "" + type "String" + since "5.0.0" + } + field { + name "vmCategory" + desc "" + type "String" + since "5.0.0" + } + field { + name "architecture" + desc "" + type "String" + since "5.0.0" + } + field { + name "schemaVersion" + desc "" + type "String" + since "5.0.0" + } + field { + name "metadataPath" + desc "" + type "String" + since "5.0.0" + } + field { + name "hostUuid" + desc "" + type "String" + since "5.0.0" + } + field { + name "sizeBytes" + desc "" + type "Long" + since "5.0.0" + } + field { + name "lastUpdateTime" + desc "" + type "Long" + since "5.0.0" + } + field { + name "incomplete" + desc "" + type "boolean" + since "5.0.0" + } +} 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..4711fa5de74 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 = "SnapshotUuidToVmUuidResolver", field = "uuid", updateOnFailure = true) 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..6ae3008e03e 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 = "SnapshotUuidToVmUuidResolver", field = "uuid", updateOnFailure = true) 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..f8a1a670023 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 = "SnapshotUuidToVmUuidResolver", field = "uuid") 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..6daf7b61649 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 = "SnapshotUuidToVmUuidResolver", field = "uuid") 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/CreateVolumesSnapshotMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotMsg.java index 886f697c6df..919e0b9af8e 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotMsg.java @@ -16,6 +16,8 @@ public class CreateVolumesSnapshotMsg extends NeedReplyMessage implements NeedQu private List volumeSnapshotJobs; + private boolean backupHostFileIfNeeded; + public String getAccountUuid() { return accountUuid; } @@ -44,4 +46,12 @@ public ConsistentType getConsistentType() { public void setConsistentType(ConsistentType consistentType) { this.consistentType = consistentType; } + + public boolean isBackupHostFileIfNeeded() { + return backupHostFileIfNeeded; + } + + public void setBackupHostFileIfNeeded(boolean backupHostFileIfNeeded) { + this.backupHostFileIfNeeded = backupHostFileIfNeeded; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java index 9e52d36b0d3..a9b4f6bdc62 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerMsg.java @@ -19,6 +19,8 @@ public class CreateVolumesSnapshotOverlayInnerMsg extends NeedReplyMessage imple private List lockedVolumeUuids; + private boolean backupHostFileIfNeeded; + public List getLockedVmInstanceUuids() { return lockedVmInstanceUuids; } @@ -63,4 +65,12 @@ public ConsistentType getConsistentType() { public void setConsistentType(ConsistentType consistentType) { this.consistentType = consistentType; } + + public boolean isBackupHostFileIfNeeded() { + return backupHostFileIfNeeded; + } + + public void setBackupHostFileIfNeeded(boolean backupHostFileIfNeeded) { + this.backupHostFileIfNeeded = backupHostFileIfNeeded; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerReply.java b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerReply.java index a488079afc2..cd816f6014c 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerReply.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotOverlayInnerReply.java @@ -9,6 +9,7 @@ */ public class CreateVolumesSnapshotOverlayInnerReply extends MessageReply { private List inventories; + private List hostBackupFileUuidList; public List getInventories() { return inventories; @@ -17,4 +18,12 @@ public List getInventories() { public void setInventories(List inventories) { this.inventories = inventories; } + + public List getHostBackupFileUuidList() { + return hostBackupFileUuidList; + } + + public void setHostBackupFileUuidList(List hostBackupFileUuidList) { + this.hostBackupFileUuidList = hostBackupFileUuidList; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotReply.java b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotReply.java index 41e1b0270f9..ac510f260b7 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotReply.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/CreateVolumesSnapshotReply.java @@ -9,6 +9,7 @@ */ public class CreateVolumesSnapshotReply extends MessageReply { private List inventories; + private List hostBackupFileUuidList; public List getInventories() { return inventories; @@ -17,4 +18,12 @@ public List getInventories() { public void setInventories(List inventories) { this.inventories = inventories; } + + public List getHostBackupFileUuidList() { + return hostBackupFileUuidList; + } + + public void setHostBackupFileUuidList(List hostBackupFileUuidList) { + this.hostBackupFileUuidList = hostBackupFileUuidList; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmMsg.java b/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmMsg.java index a54e8a2b8cf..c5199404ad7 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmMsg.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmMsg.java @@ -2,6 +2,7 @@ import org.zstack.header.host.HostMessage; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.additions.VmHostFileBackupJob; import java.util.List; @@ -11,6 +12,7 @@ public class TakeVolumesSnapshotOnKvmMsg extends NeedReplyMessage implements HostMessage { private List snapshotJobs; private String hostUuid; + private List vmHostFileBackupJobs; @Override public String getHostUuid() { @@ -28,4 +30,13 @@ public List getSnapshotJobs() { public void setSnapshotJobs(List snapshotJobs) { this.snapshotJobs = snapshotJobs; } + + public List getVmHostFileBackupJobs() { + return vmHostFileBackupJobs; + } + + public void setVmHostFileBackupJobs(List vmHostFileBackupJobs) { + this.vmHostFileBackupJobs = vmHostFileBackupJobs; + } + } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmReply.java b/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmReply.java index 2ba73944a57..bb63fc9d324 100644 --- a/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmReply.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/TakeVolumesSnapshotOnKvmReply.java @@ -9,6 +9,7 @@ */ public class TakeVolumesSnapshotOnKvmReply extends MessageReply { private List snapshotsResults; + private String hostBackupTempResourceUuid; public List getSnapshotsResults() { return snapshotsResults; @@ -17,4 +18,12 @@ public List getSnapshotsResults() { public void setSnapshotsResults(List snapshotsResults) { this.snapshotsResults = snapshotsResults; } + + public String getHostBackupTempResourceUuid() { + return hostBackupTempResourceUuid; + } + + public void setHostBackupTempResourceUuid(String hostBackupTempResourceUuid) { + this.hostBackupTempResourceUuid = hostBackupTempResourceUuid; + } } diff --git a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotConstant.java b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotConstant.java index 8f784a72051..adb5c5ea436 100755 --- a/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotConstant.java +++ b/header/src/main/java/org/zstack/header/storage/snapshot/VolumeSnapshotConstant.java @@ -1,5 +1,10 @@ package org.zstack.header.storage.snapshot; +import org.zstack.header.vm.VmInstanceState; + +import java.util.Arrays; +import java.util.List; + /** */ public interface VolumeSnapshotConstant { @@ -16,4 +21,11 @@ public interface VolumeSnapshotConstant { String VOLUME_SNAPSHOT_STRUCT = "VolumeSnapshotStruct"; String NEED_TAKE_SNAPSHOTS_ON_HYPERVISOR = "needTakeSnapshotOnHypervisor"; String NEED_BLOCK_STREAM_ON_HYPERVISOR = "needBlockStreamOnHypervisor"; + + public static final List ALLOW_TAKE_SNAPSHOTS_VM_STATES = Arrays.asList( + VmInstanceState.Running, VmInstanceState.Stopped, VmInstanceState.Paused + ); + public static final List ALLOW_TAKE_MEMORY_SNAPSHOTS_VM_STATES = Arrays.asList( + VmInstanceState.Stopped + ); } 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..cb1f8dde454 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 = "SnapshotGroupUuidToVmUuidResolver", field = "uuid", 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..28752daf8b0 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 = "SnapshotGroupUuidToVmUuidResolver", field = "uuid", 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..6428bde5269 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 = "SnapshotGroupUuidToVmUuidResolver", field = "uuid") 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..68deeb2b635 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 = "SnapshotGroupUuidToVmUuidResolver", field = "uuid") 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..aaa350d471a 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 = "ResourceUuidToVmUuidResolver", field = "resourceUuid") 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..c2fa8085b91 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 = "ResourceUuidToVmUuidResolver", field = "resourceUuid") 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..59597ac6901 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 = "SystemTagUuidToVmUuidResolver", field = "uuid") 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..65173855747 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 = "SystemTagUuidToVmUuidResolver", field = "uuid") public class APIUpdateSystemTagMsg extends APIMessage { @APIParam(resourceType = SystemTagVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/tpm/RBACInfo.java b/header/src/main/java/org/zstack/header/tpm/RBACInfo.java new file mode 100644 index 00000000000..b8b56608d86 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/RBACInfo.java @@ -0,0 +1,36 @@ +package org.zstack.header.tpm; + +import org.zstack.header.identity.rbac.RBACDescription; +import org.zstack.header.rest.SDKPackage; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.vm.VmInstanceVO; + +@SDKPackage(packageName="org.zstack.sdk.tpm") +public class RBACInfo implements RBACDescription { + @Override + public String permissionName() { + return "tpm"; + } + + @Override + public void permissions() { + permissionBuilder() + .communityAvailable() + .zsvBasicAvailable() + .zsvProAvailable() + .build(); + + resourceEnsembleContributorBuilder() + .resource(TpmVO.class) + .contributeTo(VmInstanceVO.class) + .build(); + } + + @Override + public void roles() { + roleContributorBuilder() + .actionsInThisPermission() + .toOtherRole() + .build(); + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/TpmConstants.java b/header/src/main/java/org/zstack/header/tpm/TpmConstants.java new file mode 100644 index 00000000000..4654c5c956d --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/TpmConstants.java @@ -0,0 +1,17 @@ +package org.zstack.header.tpm; + +import org.zstack.header.vm.VmInstanceState; + +import java.util.Collections; +import java.util.List; + +import static org.zstack.utils.CollectionDSL.list; + +public class TpmConstants { + private TpmConstants() {} + + public static final String SERVICE_ID = "tpm"; + + public static final List SUPPORT_VM_STATES_FOR_TPM_OPERATION = + Collections.unmodifiableList(list(VmInstanceState.Stopped)); +} diff --git a/header/src/main/java/org/zstack/header/tpm/TpmErrors.java b/header/src/main/java/org/zstack/header/tpm/TpmErrors.java new file mode 100644 index 00000000000..cb9eebb68c2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/TpmErrors.java @@ -0,0 +1,22 @@ +package org.zstack.header.tpm; + +public enum TpmErrors { + GENERAL_ERROR(1000), + + // INVALID_ARGUMENT, 17xx <- SYS.1007 + TPM_ALREADY_EXISTS(1701), + TPM_NOT_FOUND(1702), + VM_STATE_ERROR(1703), + ; + + private String code; + + private TpmErrors(int id) { + code = String.format("TPM.%s", id); + } + + @Override + public String toString() { + return code; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEvent.java b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEvent.java new file mode 100644 index 00000000000..4106a67afdd --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEvent.java @@ -0,0 +1,31 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.tpm.entity.TpmInventory; + +@RestResponse(allTo = "inventory") +public class APIAddTpmEvent extends APIEvent { + private TpmInventory inventory; + + public APIAddTpmEvent() { + } + + public APIAddTpmEvent(String apiId) { + super(apiId); + } + + public TpmInventory getInventory() { + return inventory; + } + + public void setInventory(TpmInventory inventory) { + this.inventory = inventory; + } + + public static APIAddTpmEvent __example__() { + APIAddTpmEvent event = new APIAddTpmEvent(); + event.setInventory(TpmInventory.__example__()); + return event; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..31d912bd097 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmEventDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.entity.TpmInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "虚拟机添加 TPM 的结果" + + ref { + name "inventory" + path "org.zstack.header.tpm.api.APIAddTpmEvent.inventory" + desc "TPM 信息" + type "TpmInventory" + since "5.0.0" + clz TpmInventory.class + } + field { + name "success" + desc "添加是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIAddTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsg.java new file mode 100644 index 00000000000..3acea51c0fc --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsg.java @@ -0,0 +1,51 @@ +package org.zstack.header.tpm.api; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APICreateMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.DocUtils; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.utils.StringDSL; + +@RestRequest( + path = "/tpms", + method = HttpMethod.POST, + responseClass = APIAddTpmEvent.class, + parameterName = "params" +) +public class APIAddTpmMsg extends APICreateMessage implements VmInstanceMessage { + /** + * If null, use the default key provider from global config (if set). + */ + @APIParam(required = false, minLength = 32, maxLength = 32) + private String keyProviderUuid; + + @APIParam(resourceType = VmInstanceVO.class) + private String vmInstanceUuid; + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public static APIAddTpmMsg __example__() { + APIAddTpmMsg msg = new APIAddTpmMsg(); + msg.setKeyProviderUuid(StringDSL.createFixedUuid("keyProviderUuid")); + msg.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..465d0ddd7c5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIAddTpmMsgDoc_zh_cn.groovy @@ -0,0 +1,85 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.api.APIAddTpmEvent + +doc { + title "AddTpm" + + category "tpm" + + desc """虚拟机添加 TPM""" + + rest { + request { + url "POST /v1/tpms" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIAddTpmMsg.class + + desc """""" + + params { + + column { + name "keyProviderUuid" + enclosedIn "params" + desc "密钥提供程序 UUID" + location "body" + type "String" + optional true + since "5.0.0" + } + column { + name "vmInstanceUuid" + enclosedIn "params" + desc "虚拟机 UUID" + location "body" + type "String" + optional false + since "5.0.0" + } + column { + name "resourceUuid" + enclosedIn "params" + desc "资源 UUID" + 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 APIAddTpmEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsg.java b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsg.java new file mode 100644 index 00000000000..c687bd0e576 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsg.java @@ -0,0 +1,48 @@ +package org.zstack.header.tpm.api; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.APISyncCallMessage; +import org.zstack.header.message.DocUtils; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.vm.VmInstanceVO; + +@RestRequest( + path = "/tpms/capability", + method = HttpMethod.GET, + responseClass = APIGetTpmCapabilityReply.class +) +public class APIGetTpmCapabilityMsg extends APISyncCallMessage implements TpmMessage { + @APIParam(required = false, resourceType = TpmVO.class) + private String tpmUuid; + + @APIParam(required = false, resourceType = VmInstanceVO.class) + private String vmInstanceUuid; + + @Override + public String getTpmUuid() { + return tpmUuid; + } + + @Override + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + @Override + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public static APIGetTpmCapabilityMsg __example__() { + APIGetTpmCapabilityMsg msg = new APIGetTpmCapabilityMsg(); + msg.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..d5d3bb770aa --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityMsgDoc_zh_cn.groovy @@ -0,0 +1,67 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.api.APIGetTpmCapabilityReply + +doc { + title "GetTpmCapability" + + category "tpm" + + desc """获取 TPM 详情数据""" + + rest { + request { + url "GET /v1/tpms/capability" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIGetTpmCapabilityMsg.class + + desc """""" + + params { + + column { + name "tpmUuid" + enclosedIn "" + desc "TPM UUID" + location "query" + type "String" + optional true + since "5.0.0" + } + column { + name "vmInstanceUuid" + enclosedIn "" + desc "虚拟机 UUID" + location "query" + type "String" + optional true + 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 APIGetTpmCapabilityReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReply.java b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReply.java new file mode 100644 index 00000000000..4819ccfc1aa --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReply.java @@ -0,0 +1,24 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.message.APIReply; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.tpm.entity.TpmCapabilityView; + +@RestResponse(fieldsTo = "all") +public class APIGetTpmCapabilityReply extends APIReply { + private TpmCapabilityView inventory; + + public TpmCapabilityView getInventory() { + return inventory; + } + + public void setInventory(TpmCapabilityView inventory) { + this.inventory = inventory; + } + + public static APIGetTpmCapabilityReply __example__() { + APIGetTpmCapabilityReply reply = new APIGetTpmCapabilityReply(); + reply.setInventory(TpmCapabilityView.__example__()); + return reply; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..216b1ba86b8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIGetTpmCapabilityReplyDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.entity.TpmCapabilityView +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "获取 TPM 详情数据的结果" + + ref { + name "inventory" + path "org.zstack.header.tpm.api.APIGetTpmCapabilityReply.inventory" + desc "TPM 性能和信息数据" + type "TpmCapabilityView" + since "5.0.0" + clz TpmCapabilityView.class + } + field { + name "success" + desc "获取是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIGetTpmCapabilityReply.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsg.java new file mode 100644 index 00000000000..00730943d90 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsg.java @@ -0,0 +1,26 @@ +package org.zstack.header.tpm.api; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.DocUtils; +import org.zstack.header.query.APIQueryMessage; +import org.zstack.header.query.AutoQuery; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.tpm.entity.TpmInventory; +import org.zstack.header.tpm.entity.TpmVO; + +import java.util.List; + +import static java.util.Arrays.asList; + +@AutoQuery(replyClass = APIQueryTpmReply.class, inventoryClass = TpmInventory.class) +@RestRequest( + path = "/tpms", + optionalPaths = {"/tpms/{uuid}"}, + method = HttpMethod.GET, + responseClass = APIQueryTpmReply.class +) +public class APIQueryTpmMsg extends APIQueryMessage { + public static List __example__() { + return asList("uuid=" + DocUtils.createFixedUuid(TpmVO.class)); + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..2254f22e12f --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmMsgDoc_zh_cn.groovy @@ -0,0 +1,31 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.api.APIQueryTpmReply +import org.zstack.header.query.APIQueryMessage + +doc { + title "QueryTpm" + + category "tpm" + + desc """查询 TPM""" + + rest { + request { + url "GET /v1/tpms" + url "GET /v1/tpms/{uuid}" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIQueryTpmMsg.class + + desc """""" + + params APIQueryMessage.class + } + + response { + clz APIQueryTpmReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReply.java b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReply.java new file mode 100644 index 00000000000..9262fd88081 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReply.java @@ -0,0 +1,28 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.query.APIQueryReply; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.tpm.entity.TpmInventory; + +import java.util.List; + +import static org.zstack.utils.CollectionDSL.list; + +@RestResponse(allTo = "inventories") +public class APIQueryTpmReply extends APIQueryReply { + private List inventories; + + public List getInventories() { + return inventories; + } + + public void setInventories(List inventories) { + this.inventories = inventories; + } + + public static APIQueryTpmReply __example__() { + APIQueryTpmReply reply = new APIQueryTpmReply(); + reply.setInventories(list(TpmInventory.__example__())); + return reply; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..bff2c89f924 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIQueryTpmReplyDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.entity.TpmInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "查询 TPM 的结果" + + ref { + name "inventories" + path "org.zstack.header.tpm.api.APIQueryTpmReply.inventories" + desc "TPM 列表" + type "List" + since "5.0.0" + clz TpmInventory.class + } + field { + name "success" + desc "查询是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIQueryTpmReply.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEvent.java b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEvent.java new file mode 100644 index 00000000000..7356c2aafa4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEvent.java @@ -0,0 +1,19 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse +public class APIRemoveTpmEvent extends APIEvent { + public APIRemoveTpmEvent(String apiId) { + super(apiId); + } + + public APIRemoveTpmEvent() { + super(null); + } + + public static APIRemoveTpmEvent __example__() { + return new APIRemoveTpmEvent(); + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..bd77dfe1d5f --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmEventDoc_zh_cn.groovy @@ -0,0 +1,23 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "虚拟机删除 TPM 的结果" + + field { + name "success" + desc "删除是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIRemoveTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsg.java new file mode 100644 index 00000000000..5b5bc148e09 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsg.java @@ -0,0 +1,49 @@ +package org.zstack.header.tpm.api; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIDeleteMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.DocUtils; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.vm.VmInstanceMessage; +import org.zstack.header.vm.VmInstanceVO; + +@RestRequest( + path = "/tpms", + method = HttpMethod.DELETE, + responseClass = APIRemoveTpmEvent.class +) +public class APIRemoveTpmMsg extends APIDeleteMessage implements VmInstanceMessage, TpmMessage { + @APIParam(required = false, resourceType = TpmVO.class, successIfResourceNotExisting = true) + private String tpmUuid; + + @APIParam(required = false, resourceType = VmInstanceVO.class) + private String vmInstanceUuid; + + @Override + public String getTpmUuid() { + return tpmUuid; + } + + @Override + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + @Override + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public static APIRemoveTpmMsg __example__() { + APIRemoveTpmMsg msg = new APIRemoveTpmMsg(); + msg.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..c10c5e8526d --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIRemoveTpmMsgDoc_zh_cn.groovy @@ -0,0 +1,76 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.api.APIRemoveTpmEvent + +doc { + title "RemoveTpm" + + category "tpm" + + desc """虚拟机删除 TPM""" + + rest { + request { + url "DELETE /v1/tpms" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIRemoveTpmMsg.class + + desc """""" + + params { + + column { + name "tpmUuid" + enclosedIn "" + desc "TPM UUID" + location "query" + type "String" + optional true + since "5.0.0" + } + column { + name "vmInstanceUuid" + enclosedIn "" + desc "虚拟机 UUID" + location "query" + type "String" + optional true + since "5.0.0" + } + column { + name "deleteMode" + enclosedIn "" + desc "删除模式(Permissive / Enforcing,Permissive)" + location "query" + type "String" + optional true + 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 APIRemoveTpmEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEvent.java b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEvent.java new file mode 100644 index 00000000000..3d03410e053 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEvent.java @@ -0,0 +1,31 @@ +package org.zstack.header.tpm.api; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; +import org.zstack.header.tpm.entity.TpmInventory; + +@RestResponse(allTo = "inventory") +public class APIUpdateTpmEvent extends APIEvent { + private TpmInventory inventory; + + public APIUpdateTpmEvent() { + } + + public APIUpdateTpmEvent(String apiId) { + super(apiId); + } + + public TpmInventory getInventory() { + return inventory; + } + + public void setInventory(TpmInventory inventory) { + this.inventory = inventory; + } + + public static APIUpdateTpmEvent __example__() { + APIUpdateTpmEvent event = new APIUpdateTpmEvent(); + event.setInventory(TpmInventory.__example__()); + return event; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..33f2e7dab69 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmEventDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.entity.TpmInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "更新 TPM 的结果" + + ref { + name "inventory" + path "org.zstack.header.tpm.api.APIUpdateTpmEvent.inventory" + desc "更新后的 TPM 信息" + type "TpmInventory" + since "5.0.0" + clz TpmInventory.class + } + field { + name "success" + desc "更新是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.tpm.api.APIUpdateTpmEvent.error" + desc "错误码,若不为 null,则表示操作失败, 操作成功时该字段为 null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsg.java new file mode 100644 index 00000000000..4e3f264d3c3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsg.java @@ -0,0 +1,62 @@ +package org.zstack.header.tpm.api; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.message.DocUtils; +import org.zstack.header.rest.RestRequest; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.utils.StringDSL; + +@RestRequest( + path = "/tpms", + method = HttpMethod.PUT, + isAction = true, + responseClass = APIUpdateTpmEvent.class +) +public class APIUpdateTpmMsg extends APIMessage implements TpmMessage { + @APIParam(required = false, resourceType = VmInstanceVO.class) + private String vmInstanceUuid; + + @APIParam(required = false, resourceType = TpmVO.class) + private String tpmUuid; + + @APIParam(required = false, minLength = 32, maxLength = 32) + private String keyProviderUuid; + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + @Override + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + @Override + public String getTpmUuid() { + return tpmUuid; + } + + @Override + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public static APIUpdateTpmMsg __example__() { + APIUpdateTpmMsg msg = new APIUpdateTpmMsg(); + msg.setKeyProviderUuid(StringDSL.createFixedUuid("keyProviderUuid")); + msg.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..4147d9b6554 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/APIUpdateTpmMsgDoc_zh_cn.groovy @@ -0,0 +1,76 @@ +package org.zstack.header.tpm.api + +import org.zstack.header.tpm.api.APIUpdateTpmEvent + +doc { + title "UpdateTpm" + + category "tpm" + + desc """更新 TPM""" + + rest { + request { + url "PUT /v1/tpms" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIUpdateTpmMsg.class + + desc """""" + + params { + + column { + name "vmInstanceUuid" + enclosedIn "updateTpm" + desc "虚拟机 UUID" + location "body" + type "String" + optional true + since "5.0.0" + } + column { + name "tpmUuid" + enclosedIn "updateTpm" + desc "TPM UUID" + location "body" + type "String" + optional true + since "5.0.0" + } + column { + name "keyProviderUuid" + enclosedIn "updateTpm" + desc "密钥提供程序 UUID" + location "body" + type "String" + 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 APIUpdateTpmEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/tpm/api/TpmMessage.java b/header/src/main/java/org/zstack/header/tpm/api/TpmMessage.java new file mode 100644 index 00000000000..1ae70c46381 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/api/TpmMessage.java @@ -0,0 +1,8 @@ +package org.zstack.header.tpm.api; + +public interface TpmMessage { + String getVmInstanceUuid(); + void setVmInstanceUuid(String vmInstanceUuid); + String getTpmUuid(); + void setTpmUuid(String tpmUuid); +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityView.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityView.java new file mode 100644 index 00000000000..3af24b8662c --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityView.java @@ -0,0 +1,125 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.configuration.PythonClass; +import org.zstack.header.vm.additions.VmHostFileInventory; + +import java.sql.Timestamp; +import java.util.List; + +import static org.zstack.utils.CollectionDSL.list; + +@PythonClass +public class TpmCapabilityView { + // fields in TpmInventory + private String uuid; + private String name; + private String vmInstanceUuid; + private Timestamp createDate; + private Timestamp lastOpDate; + /** + * collect VmHostFileInventory(VmHostFileVO) type=NvRam or type=TpmState + */ + private List fileRefs; + + // related table fields + // TODO keyProviderUuid / keyProviderType / keyProviderName / keyProviderKeyVersion + + // status fields : from system tags + private String edkVersion; + private String swtpmVersion; + + // config fields : from global / resource config + private boolean resetTpmAfterVmCloneConfig; + + public void setTpmInventory(TpmInventory inventory) { + setUuid(inventory.getUuid()); + setName(inventory.getName()); + setVmInstanceUuid(inventory.getVmInstanceUuid()); + setCreateDate(inventory.getCreateDate()); + setLastOpDate(inventory.getLastOpDate()); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + 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; + } + + public List getFileRefs() { + return fileRefs; + } + + public void setFileRefs(List fileRefs) { + this.fileRefs = fileRefs; + } + + public String getEdkVersion() { + return edkVersion; + } + + public void setEdkVersion(String edkVersion) { + this.edkVersion = edkVersion; + } + + public String getSwtpmVersion() { + return swtpmVersion; + } + + public void setSwtpmVersion(String swtpmVersion) { + this.swtpmVersion = swtpmVersion; + } + + public boolean isResetTpmAfterVmCloneConfig() { + return resetTpmAfterVmCloneConfig; + } + + public void setResetTpmAfterVmCloneConfig(boolean resetTpmAfterVmCloneConfig) { + this.resetTpmAfterVmCloneConfig = resetTpmAfterVmCloneConfig; + } + + public static TpmCapabilityView __example__() { + TpmCapabilityView view = new TpmCapabilityView(); + view.setTpmInventory(TpmInventory.__example__()); + view.setFileRefs(list(VmHostFileInventory.__example__())); + + view.setEdkVersion("edk2-ovmf-20220126gitbb1bba3d77-3.el8.noarch"); + view.setSwtpmVersion("0.8.2"); + + view.setResetTpmAfterVmCloneConfig(true); + return view; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityViewDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityViewDoc_zh_cn.groovy new file mode 100644 index 00000000000..4ee7ba90b8a --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmCapabilityViewDoc_zh_cn.groovy @@ -0,0 +1,66 @@ +package org.zstack.header.tpm.entity + +import java.sql.Timestamp +import org.zstack.header.vm.additions.VmHostFileInventory + +doc { + + title "TPM 详情" + + field { + name "uuid" + desc "TPM UUID" + type "String" + since "5.0.0" + } + field { + name "name" + desc "TPM 资源名称" + type "String" + since "5.0.0" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.0.0" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.0.0" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.0.0" + } + ref { + name "fileRefs" + path "org.zstack.header.tpm.entity.TpmCapabilityView.fileRefs" + desc "TPM 相关的主机侧文件或目录数据列表" + type "List" + since "5.0.0" + clz VmHostFileInventory.class + } + field { + name "edkVersion" + desc "EDK 套件版本" + type "String" + since "5.0.0" + } + field { + name "swtpmVersion" + desc "SWTPM 版本" + type "String" + since "5.0.0" + } + field { + name "resetTpmAfterVmCloneConfig" + desc "是否在虚拟机克隆后重置 TPM 状态的配置" + type "boolean" + since "5.0.0" + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmInventory.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmInventory.java new file mode 100644 index 00000000000..eafd210a61d --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmInventory.java @@ -0,0 +1,97 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.configuration.PythonClassInventory; +import org.zstack.header.message.DocUtils; +import org.zstack.header.query.ExpandedQueries; +import org.zstack.header.query.ExpandedQuery; +import org.zstack.header.search.Inventory; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceVO; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.List; + +import static org.zstack.utils.CollectionUtils.transform; + +@PythonClassInventory +@Inventory(mappingVOClass = TpmVO.class) +@ExpandedQueries({ + @ExpandedQuery(expandedField = "vmInstance", inventoryClass = VmInstanceInventory.class, + foreignKey = "vmInstanceUuid", expandedInventoryKey = "uuid"), +}) +public class TpmInventory implements Serializable { + private String uuid; + private String name; + private String vmInstanceUuid; + private Timestamp createDate; + private Timestamp lastOpDate; + + public TpmInventory() { + } + + public static TpmInventory valueOf(TpmVO vo) { + TpmInventory inv = new TpmInventory(); + inv.setUuid(vo.getUuid()); + inv.setName(vo.getResourceName()); + inv.setVmInstanceUuid(vo.getVmInstanceUuid()); + inv.setCreateDate(vo.getCreateDate()); + inv.setLastOpDate(vo.getLastOpDate()); + return inv; + } + + public static List valueOf(Collection vos) { + return transform(vos, TpmInventory::valueOf); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + 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; + } + + public static TpmInventory __example__() { + TpmInventory tpm = new TpmInventory(); + tpm.setUuid(DocUtils.createFixedUuid(TpmVO.class)); + tpm.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + tpm.setName("TPM-for-VM-" + tpm.getVmInstanceUuid()); + tpm.setCreateDate(DocUtils.timestamp()); + tpm.setLastOpDate(DocUtils.timestamp()); + return tpm; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/tpm/entity/TpmInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..f67ac502f79 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmInventoryDoc_zh_cn.groovy @@ -0,0 +1,47 @@ +package org.zstack.header.tpm.entity + +import org.zstack.header.vm.additions.VmHostFileInventory + +doc { + + title "TPM 信息" + + field { + name "uuid" + desc "TPM UUID" + type "String" + since "5.0.0" + } + field { + name "name" + desc "TPM 资源名称" + type "String" + since "5.0.0" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.0.0" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.0.0" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.0.0" + } + ref { + name "hostRefs" + path "org.zstack.header.tpm.entity.TpmInventory.hostRefs" + desc "TPM 与主机的相关数据列表" + type "List" + since "5.0.0" + clz VmHostFileInventory.class + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmSpec.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmSpec.java new file mode 100644 index 00000000000..1798a775a88 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmSpec.java @@ -0,0 +1,80 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.rest.APINoSee; +import org.zstack.utils.StringDSL; + +public class TpmSpec { + private boolean enable = true; + private String tpmUuid; + private String keyProviderUuid; + @APINoSee + private String secretUuid; + @APINoSee + private String backupFileUuid; + @APINoSee + private boolean resourceKeyCreatedNew; + @APINoSee + private String resourceKeyProviderUuid; + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getBackupFileUuid() { + return backupFileUuid; + } + + public void setBackupFileUuid(String backupFileUuid) { + this.backupFileUuid = backupFileUuid; + } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + + public boolean isResourceKeyCreatedNew() { + return resourceKeyCreatedNew; + } + + public void setResourceKeyCreatedNew(boolean resourceKeyCreatedNew) { + this.resourceKeyCreatedNew = resourceKeyCreatedNew; + } + + public String getResourceKeyProviderUuid() { + return resourceKeyProviderUuid; + } + + public void setResourceKeyProviderUuid(String resourceKeyProviderUuid) { + this.resourceKeyProviderUuid = resourceKeyProviderUuid; + } + + public static TpmSpec __example__() { + TpmSpec tpm = new TpmSpec(); + tpm.setKeyProviderUuid(StringDSL.createFixedUuid("keyProviderUuid")); + return tpm; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmVO.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmVO.java new file mode 100644 index 00000000000..e5fafea5689 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmVO.java @@ -0,0 +1,91 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.identity.OwnedByAccount; +import org.zstack.header.tag.AutoDeleteTag; +import org.zstack.header.vm.VmInstanceEO; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vo.BaseResource; +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; +import org.zstack.header.vo.SoftDeletionCascade; +import org.zstack.header.vo.SoftDeletionCascades; +import org.zstack.header.vo.ToInventory; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Transient; +import java.sql.Timestamp; + +@Entity +@Table +@BaseResource +@AutoDeleteTag +@SoftDeletionCascades({ + @SoftDeletionCascade(parent = VmInstanceEO.class, joinColumn = "vmInstanceUuid") +}) +@EntityGraph( + parents = { + @EntityGraph.Neighbour(type = VmInstanceVO.class, myField = "vmInstanceUuid", targetField = "uuid"), + } +) +public class TpmVO extends ResourceVO implements ToInventory, OwnedByAccount { + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String vmInstanceUuid; + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; + + @Transient + private String accountUuid; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + 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; + } + + @Override + public String getAccountUuid() { + return accountUuid; + } + + @Override + public void setAccountUuid(String accountUuid) { + this.accountUuid = accountUuid; + } + + public TpmVO() { + } + + @Override + public String toString() { + return "TpmVO{" + + "vmInstanceUuid='" + vmInstanceUuid + '\'' + + ", uuid='" + uuid + '\'' + + '}'; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/entity/TpmVO_.java b/header/src/main/java/org/zstack/header/tpm/entity/TpmVO_.java new file mode 100644 index 00000000000..99fb5aa3ba5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/entity/TpmVO_.java @@ -0,0 +1,14 @@ +package org.zstack.header.tpm.entity; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(TpmVO.class) +public class TpmVO_ extends ResourceVO_ { + public static volatile SingularAttribute vmInstanceUuid; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/AddTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/message/AddTpmMsg.java new file mode 100644 index 00000000000..2971d2b06f2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/AddTpmMsg.java @@ -0,0 +1,45 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.tpm.api.APIAddTpmMsg; + +public class AddTpmMsg extends NeedReplyMessage { + private String keyProviderUuid; + private String vmInstanceUuid; + /** + * for creating TpmVO + */ + private String tpmUuid; + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public static AddTpmMsg valueOf(APIAddTpmMsg api) { + AddTpmMsg msg = new AddTpmMsg(); + msg.setKeyProviderUuid(api.getKeyProviderUuid()); + msg.setVmInstanceUuid(api.getVmInstanceUuid()); + msg.setTpmUuid(api.getResourceUuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/AddTpmReply.java b/header/src/main/java/org/zstack/header/tpm/message/AddTpmReply.java new file mode 100644 index 00000000000..0cbcc9bf1e3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/AddTpmReply.java @@ -0,0 +1,16 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.MessageReply; +import org.zstack.header.tpm.entity.TpmInventory; + +public class AddTpmReply extends MessageReply { + private TpmInventory inventory; + + public TpmInventory getInventory() { + return inventory; + } + + public void setInventory(TpmInventory inventory) { + this.inventory = inventory; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmMsg.java b/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmMsg.java new file mode 100644 index 00000000000..c68e16bc8d5 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmMsg.java @@ -0,0 +1,32 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.tpm.api.APIRemoveTpmMsg; + +public class RemoveTpmMsg extends NeedReplyMessage { + private String tpmUuid; + private String vmInstanceUuid; + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public static RemoveTpmMsg valueOf(APIRemoveTpmMsg api) { + RemoveTpmMsg msg = new RemoveTpmMsg(); + msg.setTpmUuid(api.getTpmUuid()); + msg.setVmInstanceUuid(api.getVmInstanceUuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmReply.java b/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmReply.java new file mode 100644 index 00000000000..ad2e3b13292 --- /dev/null +++ b/header/src/main/java/org/zstack/header/tpm/message/RemoveTpmReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.tpm.message; + +import org.zstack.header.message.MessageReply; + +public class RemoveTpmReply extends MessageReply { +} 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..e630c877e5c 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..681a04c6b51 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..5313e83aa0e 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..d157868793f 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..bfedeb43306 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 = "NicUuidToVmUuidResolver", field = "vmNicUuid") 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..1a38b709b25 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 = "NicUuidToVmUuidResolver", field = "vmNicUuid") 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/APICleanupVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEvent.java new file mode 100644 index 00000000000..60a441a626d --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEvent.java @@ -0,0 +1,53 @@ +package org.zstack.header.vm; + +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/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..2855bb47b72 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,42 @@ +package org.zstack.header.vm + + +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.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/APICleanupVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..e75d807c3c0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsg.java @@ -0,0 +1,33 @@ +package org.zstack.header.vm; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +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/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..6f7bd0827f4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APICleanupVmInstanceMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.vm + +import org.zstack.header.vm.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/APIConvertTemplatedVmInstanceToVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsg.java index 1a128bfaf84..dc1be500331 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 = "VmUuidDirectResolver", field = "templatedVmInstanceUuid") public class APIConvertTemplatedVmInstanceToVmInstanceMsg extends APIMessage implements VmInstanceMessage, APIAuditor { @APIParam(resourceType = TemplatedVmInstanceVO.class) private String templatedVmInstanceUuid; @@ -21,6 +23,9 @@ public class APIConvertTemplatedVmInstanceToVmInstanceMsg extends APIMessage imp @APIParam(maxLength = 255, required = true) private String name; + @APIParam(required = false) + private Boolean resetTpm; + @APINoSee private String vmInstanceUuid; @@ -40,6 +45,14 @@ public void setName(String name) { this.name = name; } + public Boolean getResetTpm() { + return resetTpm; + } + + public void setResetTpm(Boolean resetTpm) { + this.resetTpm = resetTpm; + } + @Override public String getVmInstanceUuid() { return vmInstanceUuid; diff --git a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsgDoc_zh_cn.groovy index 554439a51d0..1f20e124b8c 100644 --- a/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/vm/APIConvertTemplatedVmInstanceToVmInstanceMsgDoc_zh_cn.groovy @@ -57,6 +57,15 @@ doc { optional true since "zsv 4.2.6" } + column { + name "resetTpm" + enclosedIn "params" + desc "转换成虚拟机后是否重置 TPM 状态" + location "body" + type "Boolean" + optional true + since "5.0.0" + } } } 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..d83709a1703 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java index 2d27e9b264f..94d63ca232a 100644 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsg.java @@ -115,6 +115,9 @@ public class APICreateVmInstanceFromVolumeSnapshotGroupMsg extends APICreateMess @APINoSee private String platform; + @APIParam(required = false) + private Boolean resetTpm; + public String getName() { return name; } @@ -292,4 +295,12 @@ public String getPlatform() { public void setPlatform(String platform) { this.platform = platform; } + + public Boolean getResetTpm() { + return resetTpm; + } + + public void setResetTpm(Boolean resetTpm) { + this.resetTpm = resetTpm; + } } diff --git a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsgDoc_zh_cn.groovy index b2ba50497ec..e8262a13931 100644 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceFromVolumeSnapshotGroupMsgDoc_zh_cn.groovy @@ -221,6 +221,15 @@ doc { optional true since "4.10.10" } + column { + name "resetTpm" + enclosedIn "params" + desc "是否重置 TPM 状态" + location "body" + type "Boolean" + optional true + since "5.0.0" + } } } diff --git a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java index f6a924c477d..e085a0a0239 100755 --- a/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/APICreateVmInstanceMsg.java @@ -10,11 +10,14 @@ import org.zstack.header.message.*; import org.zstack.header.network.l3.L3NetworkVO; import org.zstack.header.other.APIAuditor; +import org.zstack.header.rest.APINoSee; import org.zstack.header.rest.RestRequest; import org.zstack.header.storage.primary.PrimaryStorageVO; import org.zstack.header.tag.TagResourceType; +import org.zstack.header.vm.devices.VmDevicesSpec; import org.zstack.header.volume.VolumeVO; import org.zstack.header.zone.ZoneVO; +import org.zstack.utils.gson.JSONObjectUtil; import java.util.Collections; import java.util.List; @@ -225,6 +228,15 @@ public class APICreateVmInstanceMsg extends APICreateMessage implements APIAudit @APIParam(required = false) private List diskAOs; + @APIParam(required = false) + private Map devices; + + /** + * cache of {@link #devices} + */ + @APINoSee + private VmDevicesSpec devicesSpec; + public List getDiskAOs() { return diskAOs; } @@ -466,6 +478,26 @@ public void setAllocatorStrategy(String allocatorStrategy) { this.allocatorStrategy = allocatorStrategy; } + public Map getDevices() { + return devices; + } + + public void setDevices(Map devices) { + this.devices = devices; + } + + public VmDevicesSpec getDevicesSpec() { + if (devicesSpec == null && devices != null) { + devicesSpec = JSONObjectUtil.rehashObject(devices, VmDevicesSpec.class); + } + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } + + @SuppressWarnings("unchecked") public static APICreateVmInstanceMsg __example__() { APICreateVmInstanceMsg msg = new APICreateVmInstanceMsg(); msg.setName("vm1"); @@ -489,6 +521,7 @@ public static APICreateVmInstanceMsg __example__() { disk2.setPrimaryStorageUuid(uuid(PrimaryStorageVO.class)); msg.setDiskAOs(list(disk1, disk2)); + msg.setDevices(JSONObjectUtil.rehashObject(VmDevicesSpec.__example__(), Map.class)); return msg; } 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..27d27afa839 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 = "VmUuidDirectResolver", field = "uuid") 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..f7b0cb056a1 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 = "VmUuidDirectResolver", field = "uuid") 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..89aaa4a1e95 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 = "VmUuidDirectResolver", field = "uuid") 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/APIDeleteVmSshKeyMsg.java b/header/src/main/java/org/zstack/header/vm/APIDeleteVmSshKeyMsg.java index 0372c7526ab..f10c7ed7e83 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 = "VmUuidDirectResolver", field = "uuid") 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..2f1312fb903 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..9fa37d94788 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.CONFIG, resolver = "VmUuidDirectResolver", field = "uuid") 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..3cffd78123a 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..e226e8f962b 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 = "NicUuidToVmUuidResolver", field = "vmNicUuid") 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/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java new file mode 100644 index 00000000000..89087ba98cd --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEvent.java @@ -0,0 +1,30 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse(fieldsTo = {"all"}) +public class APIGetVmInstanceMetadataFromPrimaryStorageEvent extends APIEvent { + private String metadata; + + public APIGetVmInstanceMetadataFromPrimaryStorageEvent() { + super(null); + } + + public APIGetVmInstanceMetadataFromPrimaryStorageEvent(String apiId) { + super(apiId); + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public static APIGetVmInstanceMetadataFromPrimaryStorageEvent __example__() { + APIGetVmInstanceMetadataFromPrimaryStorageEvent evt = new APIGetVmInstanceMetadataFromPrimaryStorageEvent(); + return evt; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..8521f4ca708 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageEventDoc_zh_cn.groovy @@ -0,0 +1,29 @@ +package org.zstack.header.vm + +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.APIGetVmInstanceMetadataFromPrimaryStorageEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null" + type "ErrorCode" + since "5.0.0" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java new file mode 100644 index 00000000000..686bb1ba587 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsg.java @@ -0,0 +1,35 @@ +package org.zstack.header.vm; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +@RestRequest( + path = "/primary-storage/vm-instances/metadata", + method = HttpMethod.GET, + responseClass = APIGetVmInstanceMetadataFromPrimaryStorageEvent.class +) +public class APIGetVmInstanceMetadataFromPrimaryStorageMsg extends APIMessage 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/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..560a4494010 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIGetVmInstanceMetadataFromPrimaryStorageMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.vm + +import org.zstack.header.vm.APIGetVmInstanceMetadataFromPrimaryStorageEvent + +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 APIGetVmInstanceMetadataFromPrimaryStorageEvent.class + } + } +} \ No newline at end of file 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..76e400e7dd7 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.CONFIG, resolver = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..fd0f4c44622 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 = "VmUuidDirectResolver", field = "uuid") 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/APIRegisterVmInstanceFromMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEvent.java new file mode 100644 index 00000000000..b2d721412ab --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEvent.java @@ -0,0 +1,34 @@ +package org.zstack.header.vm; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +@RestResponse(fieldsTo = {"all"}) +public class APIRegisterVmInstanceFromMetadataEvent extends APIEvent { + private VmInstanceInventory inventory; + + public APIRegisterVmInstanceFromMetadataEvent() { + super(null); + } + + public APIRegisterVmInstanceFromMetadataEvent(String apiId) { + super(apiId); + } + + public VmInstanceInventory getInventory() { + return inventory; + } + + public void setInventory(VmInstanceInventory inventory) { + this.inventory = inventory; + } + + 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/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..3ce91baf280 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.vm + + +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "从元数据注册云主机返回" + + ref { + name "inventory" + path "org.zstack.header.vm.APIRegisterVmInstanceFromMetadataEvent.inventory" + desc "云主机详情" + type "VmInstanceInventory" + since "5.0.0" + clz VmInstanceInventory.class + } + field { + name "success" + desc "操作是否成功" + type "boolean" + since "5.0.0" + } + ref { + name "error" + path "org.zstack.header.vm.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/APIRegisterVmInstanceFromMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsg.java new file mode 100644 index 00000000000..f900a635429 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsg.java @@ -0,0 +1,102 @@ +package org.zstack.header.vm; + +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.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 = 1) +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(required = false, 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(); + String vmUuid = uuid(VmInstanceVO.class); + msg.metadataPath = String.format("/mnt/ps/vm-metadata/%s.vmmeta", vmUuid); + 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/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..26f687524ac --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIRegisterVmInstanceFromMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,112 @@ +package org.zstack.header.vm + +import org.zstack.header.vm.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 true + 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 + } + } +} \ No newline at end of file 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..dc78a69c631 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..e96b38cd64b 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 = "VmUuidDirectResolver", field = "uuid") 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..7400901f35c 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 = "VmUuidDirectResolver", field = "uuid") 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..bf1d090f845 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..2aa0026eb79 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 = "VmUuidDirectResolver", field = "uuid") 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..30c886d2833 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 = "VmUuidDirectResolver", field = "uuid") 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..2ea204e2f29 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 = "VmUuidDirectResolver", field = "uuid") 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..e83d4662015 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 = "VmUuidDirectResolver", field = "uuid") 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..3c348568865 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 = "VmUuidDirectResolver", field = "uuid") 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..fc3bdf81f03 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 = "VmUuidDirectResolver", field = "uuid") 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..c82b4064519 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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/APITakeVmConsoleScreenshotEvent.java b/header/src/main/java/org/zstack/header/vm/APITakeVmConsoleScreenshotEvent.java index b03cad02a3f..a7ee8d9da57 100644 --- a/header/src/main/java/org/zstack/header/vm/APITakeVmConsoleScreenshotEvent.java +++ b/header/src/main/java/org/zstack/header/vm/APITakeVmConsoleScreenshotEvent.java @@ -1,5 +1,6 @@ package org.zstack.header.vm; +import org.zstack.header.log.NoLogging; import org.zstack.header.message.APIEvent; import org.zstack.header.rest.RestResponse; @@ -9,6 +10,7 @@ */ @RestResponse(fieldsTo = {"imageData"}) public class APITakeVmConsoleScreenshotEvent extends APIEvent { + @NoLogging(type = NoLogging.Type.LongText) private String imageData; public APITakeVmConsoleScreenshotEvent() { diff --git a/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEvent.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEvent.java new file mode 100644 index 00000000000..4736470890e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEvent.java @@ -0,0 +1,19 @@ +package org.zstack.header.vm; + +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/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..5c133eed8df --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataEventDoc_zh_cn.groovy @@ -0,0 +1,23 @@ +package org.zstack.header.vm + +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.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/APIUpdateVmInstanceMetadataMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsg.java new file mode 100644 index 00000000000..e4ffabdadcc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsg.java @@ -0,0 +1,34 @@ +package org.zstack.header.vm; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +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/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..3d4db5d283a --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMetadataMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.vm + +import org.zstack.header.vm.APIUpdateVmInstanceMetadataEvent + +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 "updateVmInstanceMetadata" + 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/APIUpdateVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/APIUpdateVmInstanceMsg.java index 60e9343ff38..d9b9d333198 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 = "VmUuidDirectResolver", field = "uuid") 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..1863f7c224e 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 = "NicUuidToVmUuidResolver", field = "vmNicUuid") 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..bab95b4fafa 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.CONFIG, resolver = "VmUuidDirectResolver", field = "uuid") 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/ApplianceVmInstanceCreateExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/ApplianceVmInstanceCreateExtensionPoint.java new file mode 100644 index 00000000000..ef723f29a1d --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/ApplianceVmInstanceCreateExtensionPoint.java @@ -0,0 +1,5 @@ +package org.zstack.header.vm; + +public interface ApplianceVmInstanceCreateExtensionPoint { + default void afterPersistApplianceVmInstanceVO(VmInstanceVO vo) {} +} diff --git a/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java index 037d9f8b323..5b6f04dac3a 100755 --- a/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/CreateVmInstanceMsg.java @@ -2,6 +2,7 @@ import org.zstack.header.configuration.VmCustomSpecificationStruct; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.devices.VmDevicesSpec; import java.util.ArrayList; import java.util.List; @@ -45,6 +46,7 @@ public class CreateVmInstanceMsg extends NeedReplyMessage implements CreateVmIns private List dataDisks; private List deprecatedDataVolumeSpecs = new ArrayList<>(); private VmCustomSpecificationStruct vmCustomSpecification; + private VmDevicesSpec devicesSpec; public List getCandidatePrimaryStorageUuidsForRootVolume() { return candidatePrimaryStorageUuidsForRootVolume; @@ -363,4 +365,12 @@ public VmCustomSpecificationStruct getVmCustomSpecification() { public void setVmCustomSpecification(VmCustomSpecificationStruct vmCustomSpecification) { this.vmCustomSpecification = vmCustomSpecification; } + + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } } diff --git a/header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java b/header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java index 55a57474009..8c01f099ea2 100755 --- a/header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java +++ b/header/src/main/java/org/zstack/header/vm/InstantiateNewCreatedVmInstanceMsg.java @@ -3,6 +3,7 @@ import org.zstack.header.configuration.VmCustomSpecificationStruct; import org.zstack.header.host.CpuArchitecture; import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.devices.VmDevicesSpec; import java.util.ArrayList; import java.util.List; @@ -59,6 +60,7 @@ public void setCandidatePrimaryStorageUuidsForDataVolume(List candidateP private DiskAO rootDisk; private List dataDisks; private List deprecatedDataVolumeSpecs; + private VmDevicesSpec devicesSpec; public DiskAO getRootDisk() { return rootDisk; @@ -84,6 +86,14 @@ public void setDeprecatedDataVolumeSpecs(List deprecatedDataVolumeSpecs) this.deprecatedDataVolumeSpecs = deprecatedDataVolumeSpecs; } + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } + public List getSoftAvoidHostUuids() { return softAvoidHostUuids; } diff --git a/header/src/main/java/org/zstack/header/vm/TakeVmConsoleScreenshotReply.java b/header/src/main/java/org/zstack/header/vm/TakeVmConsoleScreenshotReply.java index 69ad7def25f..b66ceb3e96e 100644 --- a/header/src/main/java/org/zstack/header/vm/TakeVmConsoleScreenshotReply.java +++ b/header/src/main/java/org/zstack/header/vm/TakeVmConsoleScreenshotReply.java @@ -1,5 +1,6 @@ package org.zstack.header.vm; +import org.zstack.header.log.NoLogging; import org.zstack.header.message.MessageReply; /** @@ -7,6 +8,7 @@ * @date 2023-09-11 */ public class TakeVmConsoleScreenshotReply extends MessageReply { + @NoLogging(type = NoLogging.Type.LongText) private String imageData; public String getImageData() { diff --git a/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java b/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java index bde3985ce8d..330eccdaea8 100755 --- a/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java +++ b/header/src/main/java/org/zstack/header/vm/VmCanonicalEvents.java @@ -3,6 +3,7 @@ import org.zstack.header.message.NeedJsonSchema; import org.zstack.header.errorcode.ErrorCode; import java.util.Date; +import java.util.List; import java.time.LocalDateTime; /** @@ -19,6 +20,8 @@ public class VmCanonicalEvents { public static final String VM_NIC_INFO_CHANGED_PATH = "/vm/nicinfo/change"; public static final String VM_NIC_INFO_DUPLICATE_PATH = "/vm/nicinfo/duplicate"; public static final String VM_NIC_INFO_IPRANGE_CONFLICT_PATH = "/vm/nicinfo/iprangeConflict"; + public static final String VM_HOST_FILE_CHANGED_PATH = "/vm/hostfile/changed"; + public static final String KVM_REPORT_VM_HOST_FILE_CHANGED = "/kvm/reportvmhostfilechanged"; @NeedJsonSchema public static class VmCrashReportData { @@ -317,4 +320,35 @@ public void setInternalIp(String internalIp) { this.internalIp = internalIp; } } + + @NeedJsonSchema + public static class VmHostFileChangedData { + private String hostUuid; + private String vmUuid; + private List types; + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public List getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } + } } diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java index 9d0efdd77f1..e09a1cefc52 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java @@ -1,10 +1,12 @@ package org.zstack.header.vm; import org.zstack.header.configuration.PythonClass; +import org.zstack.utils.data.SizeUnit; @PythonClass public interface VmInstanceConstant { String SERVICE_ID = "vmInstance"; + String SECURE_BOOT_SERVICE_ID = "secureBoot"; String ACTION_CATEGORY = "instance"; @PythonClass String USER_VM_TYPE = "UserVm"; diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java index e3bbe33bf11..a9d21d6b8e2 100644 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceCreateExtensionPoint.java @@ -1,10 +1,19 @@ package org.zstack.header.vm; /** - * Created by lining on 2019/4/17. + * Only for UserVM. + * + * Appliance VM use {@link ApplianceVmInstanceCreateExtensionPoint} */ public interface VmInstanceCreateExtensionPoint { void preCreateVmInstance(CreateVmInstanceMsg msg); - default void afterPersistVmInstanceVO(VmInstanceVO vo) {} + default void afterPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) {} + + /** + * Invoked when VM creation rolls back after + * {`@link` `#afterPersistVmInstanceVO`(VmInstanceVO, CreateVmInstanceMsg)} so extensions can + * clean up any state created in that hook. Implementations should be idempotent. + */ + default void afterRollbackPersistVmInstanceVO(VmInstanceVO vo, CreateVmInstanceMsg msg) {} } diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java b/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java index 4732fb36d40..ac1d8ca3d66 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java @@ -17,6 +17,7 @@ import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.storage.primary.PrimaryStorageInventory; import org.zstack.header.vm.VmInstanceConstant.VmOperation; +import org.zstack.header.vm.devices.VmDevicesSpec; import org.zstack.header.volume.VolumeFormat; import org.zstack.header.volume.VolumeInventory; import org.zstack.header.volume.VolumeType; @@ -403,6 +404,8 @@ public void setCandidatePrimaryStorageUuidsForDataVolume(List candidateP private List dataDisks; private List deprecatedDisksSpecs = new ArrayList<>(); private VmCustomSpecificationStruct vmCustomSpecification; + private VmDevicesSpec devicesSpec = new VmDevicesSpec(); + private OperatingSystemBootingSpec osSpec = new OperatingSystemBootingSpec(); public DiskAO getRootDisk() { return rootDisk; @@ -436,6 +439,22 @@ public void setVmCustomSpecification(VmCustomSpecificationStruct vmCustomSpecifi this.vmCustomSpecification = vmCustomSpecification; } + public VmDevicesSpec getDevicesSpec() { + return devicesSpec; + } + + public void setDevicesSpec(VmDevicesSpec devicesSpec) { + this.devicesSpec = devicesSpec; + } + + public OperatingSystemBootingSpec getOsSpec() { + return osSpec; + } + + public void setOsSpec(OperatingSystemBootingSpec osSpec) { + this.osSpec = osSpec; + } + public boolean isSkipIpAllocation() { return skipIpAllocation; } @@ -905,4 +924,19 @@ public String getVolumeFormatFromImage() { return ImageConstant.QCOW2_FORMAT_STRING; } } + + public static class OperatingSystemBootingSpec implements Serializable { + /** + * @see VmMachineType + */ + private String machineType; + + public String getMachineType() { + return machineType; + } + + public void setMachineType(String machineType) { + this.machineType = machineType; + } + } } 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..314ac3d8b0b 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), @@ -200,6 +202,9 @@ public enum VmInstanceState { new Transaction(VmInstanceStateEvent.noState, VmInstanceState.NoState), new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying) ); + Registering.transactions( + new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped) + ); } private VmInstanceState(VmInstanceStateEvent drivenEvent) { diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceVO.java b/header/src/main/java/org/zstack/header/vm/VmInstanceVO.java index 0923c8e356a..5306142ee46 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceVO.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceVO.java @@ -5,6 +5,7 @@ import org.zstack.header.host.HostVO; import org.zstack.header.identity.OwnedByAccount; import org.zstack.header.image.ImageVO; +import org.zstack.header.tpm.entity.TpmVO; import org.zstack.header.vm.cdrom.VmCdRomVO; import org.zstack.header.vo.*; import org.zstack.header.vo.EntityGraph; @@ -36,6 +37,7 @@ @EntityGraph.Neighbour(type = VolumeVO.class, myField = "rootVolumeUuid", targetField = "uuid"), @EntityGraph.Neighbour(type = VmNicVO.class, myField = "uuid", targetField = "vmInstanceUuid"), @EntityGraph.Neighbour(type = VmCdRomVO.class, myField = "uuid", targetField = "vmInstanceUuid"), + @EntityGraph.Neighbour(type = TpmVO.class, myField = "uuid", targetField = "vmInstanceUuid"), } ) public class VmInstanceVO extends VmInstanceAO implements OwnedByAccount, ToInventory { diff --git a/header/src/main/java/org/zstack/header/vm/additions/PackageInfo.java b/header/src/main/java/org/zstack/header/vm/additions/PackageInfo.java new file mode 100644 index 00000000000..dc9921af1f4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/PackageInfo.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.rest.SDKPackage; + +@SDKPackage(packageName="org.zstack.sdk.vm.entity") +public class PackageInfo { +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmMsg.java b/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmMsg.java new file mode 100644 index 00000000000..0ce384c46c4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmMsg.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.NeedReplyMessage; + +public class ResetVmTpmMsg extends NeedReplyMessage { + private String vmInstanceUuid; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmReply.java b/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmReply.java new file mode 100644 index 00000000000..0f100658fe3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/ResetVmTpmReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.MessageReply; + +public class ResetVmTpmReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileMsg.java b/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileMsg.java new file mode 100644 index 00000000000..392e6d12e71 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileMsg.java @@ -0,0 +1,35 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.VmInstanceMessage; + +public class RestoreVmHostFileMsg extends NeedReplyMessage implements VmInstanceMessage { + private String vmInstanceUuid; + private String snapshotGroupUuid; + private String syncReason; + + @Override + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getSnapshotGroupUuid() { + return snapshotGroupUuid; + } + + public void setSnapshotGroupUuid(String snapshotGroupUuid) { + this.snapshotGroupUuid = snapshotGroupUuid; + } + + public String getSyncReason() { + return syncReason; + } + + public void setSyncReason(String syncReason) { + this.syncReason = syncReason; + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileReply.java b/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileReply.java new file mode 100644 index 00000000000..602059a45dc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/RestoreVmHostFileReply.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.message.MessageReply; + +public class RestoreVmHostFileReply extends MessageReply { +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO.java new file mode 100644 index 00000000000..383da0844a0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO.java @@ -0,0 +1,78 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Virtual Machine Host-side File Value Object (Backup files) + * + * Include: NvRam / TpmState files + */ +@Entity +@Table +@EntityGraph( + friends = { + @EntityGraph.Neighbour(type = ResourceVO.class, myField = "resourceUuid", targetField = "uuid"), + } +) +public class VmHostBackupFileVO extends ResourceVO { + @Column + private String resourceUuid; + @Column + @Enumerated(EnumType.STRING) + private VmHostFileType type; + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public VmHostFileType getType() { + return type; + } + + public void setType(VmHostFileType type) { + this.type = type; + } + + 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; + } + + @Override + public String toString() { + return "VmHostBackupFileVO{" + + "resourceUuid='" + resourceUuid + '\'' + + ", type=" + type + + ", createDate=" + createDate + + ", lastOpDate=" + lastOpDate + + '}'; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO_.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO_.java new file mode 100644 index 00000000000..355fb0661f9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostBackupFileVO_.java @@ -0,0 +1,15 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmHostBackupFileVO.class) +public class VmHostBackupFileVO_ extends ResourceVO_ { + public static volatile SingularAttribute resourceUuid; + public static volatile SingularAttribute type; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileBackupJob.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileBackupJob.java new file mode 100644 index 00000000000..4b82fc0b3b8 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileBackupJob.java @@ -0,0 +1,31 @@ +package org.zstack.header.vm.additions; + +public class VmHostFileBackupJob { + private String srcPath; + private String destPath; + private String type; + + public String getSrcPath() { + return srcPath; + } + + public void setSrcPath(String srcPath) { + this.srcPath = srcPath; + } + + public String getDestPath() { + return destPath; + } + + public void setDestPath(String destPath) { + this.destPath = destPath; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentFormat.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentFormat.java new file mode 100644 index 00000000000..1212dc054bc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentFormat.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +public enum VmHostFileContentFormat { + Raw, + TarballGzip, +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO.java new file mode 100644 index 00000000000..e08e5a41d70 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO.java @@ -0,0 +1,88 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; +import org.zstack.header.vo.SoftDeletionCascade; +import org.zstack.header.vo.SoftDeletionCascades; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Virtual Machine Host-side File Content Value Object + */ +@Entity +@Table +@SoftDeletionCascades({ + @SoftDeletionCascade(parent = ResourceVO.class, joinColumn = "uuid"), +}) +public class VmHostFileContentVO { + @Id + @Column + @ForeignKey(parentEntityClass = ResourceVO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String uuid; + @Column + private byte[] content; + @Column + @Enumerated(EnumType.STRING) + private VmHostFileContentFormat format; + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public VmHostFileContentFormat getFormat() { + return format; + } + + public void setFormat(VmHostFileContentFormat format) { + this.format = format; + } + + 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; + } + + @Override + public String toString() { + return "VmHostFileContentVO{" + + "uuid='" + uuid + '\'' + + ", format=" + format + + ", createDate=" + createDate + + ", lastOpDate=" + lastOpDate + + '}'; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO_.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO_.java new file mode 100644 index 00000000000..c375650337b --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileContentVO_.java @@ -0,0 +1,14 @@ +package org.zstack.header.vm.additions; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmHostFileContentVO.class) +public class VmHostFileContentVO_ { + public static volatile SingularAttribute uuid; + public static volatile SingularAttribute content; + public static volatile SingularAttribute format; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java new file mode 100644 index 00000000000..e229c49024c --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventory.java @@ -0,0 +1,139 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.host.HostVO; +import org.zstack.header.message.DocUtils; +import org.zstack.header.vm.VmInstanceVO; + +import java.sql.Timestamp; +import java.util.Collection; +import java.util.List; + +import static org.zstack.utils.CollectionUtils.transform; + +public class VmHostFileInventory { + private String uuid; + private String vmInstanceUuid; + private String hostUuid; + private String type; + private String path; + private String lastSyncReason; + private Timestamp changeDate; + private Timestamp lastSyncDate; + private Timestamp createDate; + private Timestamp lastOpDate; + + public VmHostFileInventory() { + } + + public static VmHostFileInventory valueOf(VmHostFileVO vo) { + VmHostFileInventory inv = new VmHostFileInventory(); + inv.setUuid(vo.getUuid()); + inv.setVmInstanceUuid(vo.getVmInstanceUuid()); + inv.setHostUuid(vo.getHostUuid()); + inv.setType(vo.getType().toString()); + inv.setPath(vo.getPath()); + inv.setLastSyncReason(vo.getLastSyncReason()); + inv.setChangeDate(vo.getChangeDate()); + inv.setLastSyncDate(vo.getLastSyncDate()); + inv.setCreateDate(vo.getCreateDate()); + inv.setLastOpDate(vo.getLastOpDate()); + return inv; + } + + public static List valueOf(Collection vos) { + return transform(vos, VmHostFileInventory::valueOf); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getLastSyncReason() { + return lastSyncReason; + } + + public void setLastSyncReason(String lastSyncReason) { + this.lastSyncReason = lastSyncReason; + } + + public Timestamp getChangeDate() { + return changeDate; + } + + public void setChangeDate(Timestamp changeDate) { + this.changeDate = changeDate; + } + + public Timestamp getLastSyncDate() { + return lastSyncDate; + } + + public void setLastSyncDate(Timestamp lastSyncDate) { + this.lastSyncDate = lastSyncDate; + } + + 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; + } + + public static VmHostFileInventory __example__() { + VmHostFileInventory ref = new VmHostFileInventory(); + ref.setUuid(DocUtils.createFixedUuid(VmHostFileVO.class)); + ref.setVmInstanceUuid(DocUtils.createFixedUuid(VmInstanceVO.class)); + ref.setHostUuid(DocUtils.createFixedUuid(HostVO.class)); + ref.setType(VmHostFileType.TpmState.toString()); + ref.setPath("/var/lib/libvirt/swtpm/" + ref.getHostUuid() + "/"); + ref.setLastSyncReason("on libvirt shutdown event"); + ref.setCreateDate(DocUtils.timestamp()); + ref.setLastOpDate(DocUtils.timestamp()); + return ref; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..0ec8eb79265 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileInventoryDoc_zh_cn.groovy @@ -0,0 +1,49 @@ +package org.zstack.header.vm.additions + +doc { + + title "虚拟机在主机侧的相关文件或目录数据" + + field { + name "uuid" + desc "相关文件 UUID" + type "String" + since "5.0.0" + } + field { + name "vmInstanceUuid" + desc "虚拟机 UUID" + type "String" + since "5.0.0" + } + field { + name "hostUuid" + desc "主机 UUID" + type "String" + since "5.0.0" + } + field { + name "type" + desc "文件类型, 按用途分类, 可能是 NvRam 或者 TpmState" + type "String" + since "5.0.0" + } + field { + name "path" + desc "主机侧相关文件或目录的路径" + type "String" + since "5.0.0" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.0.0" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.0.0" + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileOperation.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileOperation.java new file mode 100644 index 00000000000..8a7be311680 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileOperation.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm.additions; + +public enum VmHostFileOperation { + Write, + Prepare, + Delete, +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java new file mode 100644 index 00000000000..852f08e1aa1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileSyncReason.java @@ -0,0 +1,37 @@ +package org.zstack.header.vm.additions; + +public enum VmHostFileSyncReason { + PrepareRead("on prepare host file (from origin host)"), + PrepareReRead("on prepare host file (from dest host)"), + ResourceRelease("on release vm resource"), + PostMigration("on post-migration (from dest host)"), + VmShutdown("on libvirt shutdown event"), + PostClone("on post-clone (from dest host)"), + Restore("restore"), + SnapshotGroupOnlineBackup("snapshot group online backup"), + RevertSnapshot("revert snapshot"), + VolumeBackup("volume backup"), + BeforeHaStart("on before HA start (from last host)"), + PeriodicDirtyCheck("on periodic dirty check"), + PeriodicForceSync("on periodic force sync"), + ; + + public final String detail; + + private VmHostFileSyncReason(String detail) { + this.detail = detail; + } + + @Override + public String toString() { + return detail; + } + + public String reason() { + return detail; + } + + public String reason(String description) { + return description == null || description.length() == 0 ? reason() : detail + ": " + description; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileType.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileType.java new file mode 100644 index 00000000000..4416821e949 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileType.java @@ -0,0 +1,6 @@ +package org.zstack.header.vm.additions; + +public enum VmHostFileType { + NvRam, + TpmState, +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java new file mode 100644 index 00000000000..44f3d5423d1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO.java @@ -0,0 +1,140 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.host.HostEO; +import org.zstack.header.host.HostVO; +import org.zstack.header.vm.VmInstanceEO; +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Virtual Machine Host-side File Value Object + * + * Include: NvRam / TpmState files + */ +@Entity +@Table +@EntityGraph( + friends = { + @EntityGraph.Neighbour(type = VmInstanceEO.class, myField = "vmInstanceUuid", targetField = "uuid"), + @EntityGraph.Neighbour(type = HostVO.class, myField = "hostUuid", targetField = "uuid"), + } +) +public class VmHostFileVO extends ResourceVO { + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String vmInstanceUuid; + @Column + @ForeignKey(parentEntityClass = HostEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String hostUuid; + @Column + @Enumerated(EnumType.STRING) + private VmHostFileType type; + @Column + private String path; + @Column + private String lastSyncReason; + @Column + private Timestamp changeDate; + @Column + private Timestamp lastSyncDate; + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + + public String getVmInstanceUuid() { + return vmInstanceUuid; + } + + public void setVmInstanceUuid(String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public VmHostFileType getType() { + return type; + } + + public void setType(VmHostFileType type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getLastSyncReason() { + return lastSyncReason; + } + + public void setLastSyncReason(String lastSyncReason) { + this.lastSyncReason = lastSyncReason; + } + + public Timestamp getChangeDate() { + return changeDate; + } + + public void setChangeDate(Timestamp changeDate) { + this.changeDate = changeDate; + } + + public Timestamp getLastSyncDate() { + return lastSyncDate; + } + + public void setLastSyncDate(Timestamp lastSyncDate) { + this.lastSyncDate = lastSyncDate; + } + + 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; + } + + @Override + public String toString() { + return "VmHostFileVO{" + + "uuid='" + uuid + '\'' + + ", vmInstanceUuid='" + vmInstanceUuid + '\'' + + ", hostUuid='" + hostUuid + '\'' + + ", type=" + type + + ", path='" + path + '\'' + + ", lastSyncReason='" + lastSyncReason + '\'' + + ", changeDate=" + changeDate + + ", lastSyncDate=" + lastSyncDate + + ", createDate=" + createDate + + ", lastOpDate=" + lastOpDate + + '}'; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java new file mode 100644 index 00000000000..ab3546aca02 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/additions/VmHostFileVO_.java @@ -0,0 +1,20 @@ +package org.zstack.header.vm.additions; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmHostFileVO.class) +public class VmHostFileVO_ extends ResourceVO_ { + public static volatile SingularAttribute vmInstanceUuid; + public static volatile SingularAttribute hostUuid; + public static volatile SingularAttribute type; + public static volatile SingularAttribute path; + public static volatile SingularAttribute lastSyncReason; + public static volatile SingularAttribute changeDate; + public static volatile SingularAttribute lastSyncDate; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} 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..0e1a8c93430 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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/devices/NvRamSpec.java b/header/src/main/java/org/zstack/header/vm/devices/NvRamSpec.java new file mode 100644 index 00000000000..81eca6330bc --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/devices/NvRamSpec.java @@ -0,0 +1,25 @@ +package org.zstack.header.vm.devices; + +import org.zstack.header.rest.APINoSee; + +public class NvRamSpec { + private boolean needRegister; + @APINoSee + private String backupFileUuid; + + public boolean isNeedRegister() { + return needRegister; + } + + public void setNeedRegister(boolean needRegister) { + this.needRegister = needRegister; + } + + public String getBackupFileUuid() { + return backupFileUuid; + } + + public void setBackupFileUuid(String backupFileUuid) { + this.backupFileUuid = backupFileUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/vm/devices/VmDevicesSpec.java b/header/src/main/java/org/zstack/header/vm/devices/VmDevicesSpec.java new file mode 100644 index 00000000000..11fc43352fa --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/devices/VmDevicesSpec.java @@ -0,0 +1,33 @@ +package org.zstack.header.vm.devices; + +import org.zstack.header.tpm.entity.TpmSpec; + +public class VmDevicesSpec { + /** + * Default value of tpm must be null, because tpm.enable is true by default. + */ + private TpmSpec tpm; + private NvRamSpec nvRam; + + public TpmSpec getTpm() { + return tpm; + } + + public void setTpm(TpmSpec tpm) { + this.tpm = tpm; + } + + public NvRamSpec getNvRam() { + return nvRam; + } + + public void setNvRam(NvRamSpec nvRam) { + this.nvRam = nvRam; + } + + public static VmDevicesSpec __example__() { + VmDevicesSpec spec = new VmDevicesSpec(); + spec.setTpm(TpmSpec.__example__()); + return spec; + } +} 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..96d6dc3238a --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/MetadataImpact.java @@ -0,0 +1,34 @@ +package org.zstack.header.vm.metadata; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetadataImpact { + Impact value(); + + /** + * Spring bean id of the {@code VmUuidFromApiResolver} implementation. + * Required when value is CONFIG or STORAGE; leave default for NONE. + */ + String resolver() default ""; + + /** + * The API message field that the resolver reads to extract the VM UUID. + * Required when value is CONFIG or STORAGE; leave default for NONE. + */ + String field() default ""; + + /** + * Whether to update metadata even if the API execution fails. + * Required when value is CONFIG or STORAGE; leave default for NONE. + */ + boolean updateOnFailure() default false; + + enum Impact { + NONE, + CONFIG, + STORAGE + } +} 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..e450d65f3f6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/ResourceMetadata.java @@ -0,0 +1,40 @@ +package org.zstack.header.vm.metadata; + +public class ResourceMetadata { + private String resourceUuid; + private String vo; + private String systemTags; + private String resourceConfigs; + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public String getVo() { + return vo; + } + + public void setVo(String vo) { + this.vo = vo; + } + + public String getSystemTags() { + return systemTags; + } + + public void setSystemTags(String systemTags) { + this.systemTags = systemTags; + } + + public String getResourceConfigs() { + return resourceConfigs; + } + + public void setResourceConfigs(String resourceConfigs) { + this.resourceConfigs = 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..498449d22c9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataConstants.java @@ -0,0 +1,39 @@ +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"; + + private static final String[] STORAGE_PATH_MARKERS = { + "/rootVolumes/", "/dataVolumes/", "/volumeSnapshots/", "/memory/" + }; + + /** + * Extract the storage mount-point prefix from a volume install path. + *

+     *   /mnt/ps/rootVolumes/acct-xxx/vol-xxx/xxx.qcow2  →  /mnt/ps/
+     *   /local_ps/dataVolumes/acct-xxx/vol-xxx/xxx.qcow2 →  /local_ps/
+     * 
+ * Uses {@code lastIndexOf} so that mount paths which themselves contain a + * marker string (e.g. {@code /rootVolumes/rootVolumes/…}) are handled + * correctly — the last occurrence is always the real subdirectory boundary. + */ + public static String extractOldPrefix(String path) { + if (path == null || !path.startsWith("/")) { + return null; + } + + int latest = -1; + for (String marker : STORAGE_PATH_MARKERS) { + int idx = path.lastIndexOf(marker); + if (idx > latest) { + latest = idx; + } + } + return latest < 0 ? null : path.substring(0, latest + 1); + } +} \ 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..311ddbde784 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmInstanceMetadataDTO.java @@ -0,0 +1,78 @@ +package org.zstack.header.vm.metadata; + +import java.util.List; + +public class VmInstanceMetadataDTO { + private String schemaVersion; + private VmMetadataCategory vmCategory; + private ResourceMetadata vm; + private List volumes; + private List nics; + private List snapshots; + private List snapshotGroups; + private List snapshotGroupRefs; + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public VmMetadataCategory getVmCategory() { + return vmCategory; + } + + public void setVmCategory(VmMetadataCategory vmCategory) { + this.vmCategory = vmCategory; + } + + public ResourceMetadata getVm() { + return vm; + } + + public void setVm(ResourceMetadata vm) { + this.vm = vm; + } + + public List getVolumes() { + return volumes; + } + + public void setVolumes(List volumes) { + this.volumes = volumes; + } + + public List getNics() { + return nics; + } + + public void setNics(List nics) { + this.nics = nics; + } + + public List getSnapshots() { + return snapshots; + } + + public void setSnapshots(List snapshots) { + this.snapshots = snapshots; + } + + public List getSnapshotGroups() { + return snapshotGroups; + } + + public void setSnapshotGroups(List snapshotGroups) { + this.snapshotGroups = snapshotGroups; + } + + public List getSnapshotGroupRefs() { + return snapshotGroupRefs; + } + + public void setSnapshotGroupRefs(List snapshotGroupRefs) { + this.snapshotGroupRefs = 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..a2a09028d27 --- /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 { + VM, + VM_TEMPLATE, + VM_TEMPLATE_CACHE +} 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..d894bb756c3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataDirtyVO.java @@ -0,0 +1,120 @@ +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 +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 = 1; + + @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/VmMetadataFlushStateVO.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO.java new file mode 100644 index 00000000000..fdf9d8f300a --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO.java @@ -0,0 +1,137 @@ +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; + +/** + * Records the metadata flush state for each VM instance, and tracks + * the stale-recovery lifecycle when a flush fails permanently. + * + *

Lifecycle

+ *
    + *
  1. Normal flush success: {@code metadataSnapshot} is updated, + * {@code pendingStaleRecovery} and {@code staleRecoveryCount} are reset to 0.
  2. + *
  3. Flush failure (retry exhausted): {@code pendingStaleRecovery} is set to {@code true}, + * the dirty row is deleted, and the VM enters the stale-recovery queue.
  4. + *
  5. Stale recovery task picks up the VM, calls {@code markDirty} to re-enter + * the flush flow, and increments {@code staleRecoveryCount}.
  6. + *
  7. Circuit breaker: when {@code staleRecoveryCount >= maxCycles}, + * both fields are reset to 0 (permanent-stale), requiring manual intervention + * via {@code APIUpdateVmInstanceMetadataMsg}.
  8. + *
+ * + *

Content drift detection

+ * A periodic task compares live-computed metadata against {@code metadataSnapshot}. + * If they differ, the VM is marked dirty for re-flush. + */ +@Entity +@Table +public class VmMetadataFlushStateVO { + @Id + @Column + @ForeignKey(parentEntityClass = VmInstanceEO.class, onDeleteAction = ReferenceOption.CASCADE) + private String vmInstanceUuid; + + /** + * Timestamp when the last successful flush completed. + * Null if the VM has never been flushed. + */ + @Column + private Timestamp lastFlushFinishTime; + + /** + * Whether this VM is waiting for the stale-recovery periodic task to + * re-queue it into the flush pipeline. Set to {@code true} when flush + * retry is exhausted; cleared on next successful flush or circuit break. + */ + @Column + private boolean pendingStaleRecovery; + + /** + * Number of stale-recovery cycles attempted so far. + * Incremented each time the recovery task re-queues this VM. + * When it reaches {@code maxCycles}, the circuit breaker fires + * and resets both this field and {@code pendingStaleRecovery} to 0. + */ + @Column + private int staleRecoveryCount; + + /** + * JSON snapshot of the VM metadata written during the last successful flush. + * Used by the content-drift detector to compare against live-computed metadata. + */ + @Column + @Lob + private String metadataSnapshot; + + @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 Timestamp getLastFlushFinishTime() { + return lastFlushFinishTime; + } + + public void setLastFlushFinishTime(Timestamp lastFlushFinishTime) { + this.lastFlushFinishTime = lastFlushFinishTime; + } + + public boolean isPendingStaleRecovery() { + return pendingStaleRecovery; + } + + public void setPendingStaleRecovery(boolean pendingStaleRecovery) { + this.pendingStaleRecovery = pendingStaleRecovery; + } + + 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; + } + + 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/VmMetadataFlushStateVO_.java b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO_.java new file mode 100644 index 00000000000..945a4557523 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataFlushStateVO_.java @@ -0,0 +1,16 @@ +package org.zstack.header.vm.metadata; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmMetadataFlushStateVO.class) +public class VmMetadataFlushStateVO_ { + public static volatile SingularAttribute vmInstanceUuid; + public static volatile SingularAttribute lastFlushFinishTime; + public static volatile SingularAttribute pendingStaleRecovery; + public static volatile SingularAttribute staleRecoveryCount; + public static volatile SingularAttribute metadataSnapshot; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} 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..b386c900e54 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathBuildExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.vm.metadata; + +public interface VmMetadataPathBuildExtensionPoint { + String getPrimaryStorageType(); + String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid); + String buildMetadataDir(String primaryStorageUuid); + String validateMetadataPath(String primaryStorageUuid, String path); + default boolean requireHostForCleanup() { + return false; + } +} 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..989175ffe97 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataPathReplacementExtensionPoint.java @@ -0,0 +1,42 @@ +package org.zstack.header.vm.metadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface VmMetadataPathReplacementExtensionPoint { + String getPrimaryStorageType(); + PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths); + class PathReplacementResult { + private Map metadataToCurrentPathMap = new HashMap<>(); + private String oldPrefix; + private String newPrefix; + + public PathReplacementResult() { + } + + public Map getMetadataToCurrentPathMap() { + return metadataToCurrentPathMap; + } + + public void setMetadataToCurrentPathMap(Map metadataToCurrentPathMap) { + this.metadataToCurrentPathMap = metadataToCurrentPathMap; + } + + 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..1819560c93e --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmMetadataResourcePersistExtensionPoint.java @@ -0,0 +1,16 @@ +package org.zstack.header.vm.metadata; + +import java.sql.Timestamp; +import java.util.List; + +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); + + default void afterRegistrationRollback(List resourceUuids) {} +} 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..afc0f4f475d --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VmUuidFromApiResolver.java @@ -0,0 +1,25 @@ +package org.zstack.header.vm.metadata; + +import java.util.ArrayList; +import java.util.List; + +public interface VmUuidFromApiResolver { + String resolveVmUuid(String fieldValue); + + default List batchResolveVmUuids(List fieldValues) { + List result = new ArrayList<>(); + if (fieldValues == null || fieldValues.isEmpty()) { + return result; + } + for (String v : fieldValues) { + if (v == null) { + continue; + } + String vmUuid = resolveVmUuid(v); + if (vmUuid != null) { + result.add(vmUuid); + } + } + return result; + } +} 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..76ff75474e1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/metadata/VolumeResourceMetadata.java @@ -0,0 +1,22 @@ +package org.zstack.header.vm.metadata; + +public class VolumeResourceMetadata extends ResourceMetadata { + private String snapshotReference; + private String snapshotReferenceTree; + + public String getSnapshotReference() { + return snapshotReference; + } + + public void setSnapshotReference(String snapshotReference) { + this.snapshotReference = snapshotReference; + } + + public String getSnapshotReferenceTree() { + return snapshotReferenceTree; + } + + public void setSnapshotReferenceTree(String snapshotReferenceTree) { + this.snapshotReferenceTree = snapshotReferenceTree; + } +} 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..b01ff703a4c 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 = "VmUuidDirectResolver", field = "vmInstanceUuid") 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..464436a6345 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..13434495343 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 = "VolumeUuidToVmUuidResolver", field = "rootVolumeUuid", 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..ca92ecf5034 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 = "VolumeUuidToVmUuidResolver", field = "volumeUuid", 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..bde41313f8e 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..65374886a04 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..53071f40ae8 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..31a76c2451c 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 = "VolumeUuidToVmUuidResolver", field = "uuid", updateOnFailure = true) 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..11fc833074d 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..0eb69ce5dc4 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 = "VolumeUuidToVmUuidResolver", field = "uuid") 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..6c6b5513525 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 = "VolumeUuidToVmUuidResolver", field = "uuid", updateOnFailure = true) 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..a3d67b10f73 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 = "VolumeUuidToVmUuidResolver", field = "uuid") public class APIUpdateVolumeMsg extends APIMessage implements VolumeMessage { @APIParam(resourceType = VolumeVO.class) private String uuid; diff --git a/header/src/main/java/org/zstack/header/volume/VolumeType.java b/header/src/main/java/org/zstack/header/volume/VolumeType.java index e373e5aef98..6c14a18bd3d 100755 --- a/header/src/main/java/org/zstack/header/volume/VolumeType.java +++ b/header/src/main/java/org/zstack/header/volume/VolumeType.java @@ -4,5 +4,5 @@ public enum VolumeType { Root, Data, Memory, - Cache + Cache, } diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/CreateApplianceVmJob.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/CreateApplianceVmJob.java index 98419fcc8a2..48e3f429ab4 100755 --- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/CreateApplianceVmJob.java +++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/CreateApplianceVmJob.java @@ -177,8 +177,8 @@ protected ApplianceVmVO scripts() { } final ApplianceVmVO finalVO = avo; - pluginRgty.getExtensionList(VmInstanceCreateExtensionPoint.class).forEach( - extension -> extension.afterPersistVmInstanceVO(finalVO)); + pluginRgty.getExtensionList(ApplianceVmInstanceCreateExtensionPoint.class).forEach( + extension -> extension.afterPersistApplianceVmInstanceVO(finalVO)); trigger.next(); } diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java index 9429bf63645..a04a9aba6e4 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java @@ -1729,7 +1729,14 @@ private void createEmptyVolume(final InstantiateVolumeOnPrimaryStorageMsg msg) { cmd.size = msg.getVolume().getSize(); cmd.setShareable(msg.getVolume().isShareable()); cmd.skipIfExisting = msg.isSkipIfExisting(); - cmd.format = msg.hasSystemTag(VolumeSystemTags.FORMAT_QCOW2.getTagFormat()) ? VolumeConstant.VOLUME_FORMAT_QCOW2 : VolumeConstant.VOLUME_FORMAT_RAW ; + + VolumeType type = Q.New(VolumeVO.class) + .eq(VolumeVO_.uuid, volumeUuid) + .select(VolumeVO_.type) + .findValue(); + cmd.format = msg.hasSystemTag(VolumeSystemTags.FORMAT_QCOW2.getTagFormat()) ? + VolumeConstant.VOLUME_FORMAT_QCOW2 : + VolumeConstant.VOLUME_FORMAT_RAW ; final InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); diff --git a/plugin/kvm/pom.xml b/plugin/kvm/pom.xml index 4cabba3d897..977ff26f496 100755 --- a/plugin/kvm/pom.xml +++ b/plugin/kvm/pom.xml @@ -4,7 +4,7 @@ plugin org.zstack - 4.10.0 + 4.10.0 .. kvm diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/BootKvmStartVmExtension.java b/plugin/kvm/src/main/java/org/zstack/kvm/BootKvmStartVmExtension.java new file mode 100644 index 00000000000..9bbc737a1f7 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/BootKvmStartVmExtension.java @@ -0,0 +1,73 @@ +package org.zstack.kvm; + +import org.apache.commons.lang.StringUtils; +import org.zstack.compute.vm.VmSystemTags; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.tag.SystemTagCreator; + +import static org.zstack.kvm.KVMSystemTags.EDK_RPM_TOKEN; +import static org.zstack.kvm.KVMSystemTags.VM_EDK; +import static org.zstack.utils.CollectionDSL.e; +import static org.zstack.utils.CollectionDSL.map; + +/** + * author:kaicai.hu + * Date:2019/12/25 + */ +public class BootKvmStartVmExtension implements KVMStartVmExtensionPoint, KVMSyncVmDeviceInfoExtensionPoint { + + @Override + public void startVmOnKvmSuccess(KVMHostInventory host, VmInstanceSpec spec) { + if (VmSystemTags.BOOT_ORDER_ONCE.hasTag(spec.getVmInventory().getUuid(), VmInstanceVO.class)) { + VmSystemTags.BOOT_ORDER.delete(spec.getVmInventory().getUuid()); + VmSystemTags.BOOT_ORDER_ONCE.delete(spec.getVmInventory().getUuid()); + } + if (VmSystemTags.CDROM_BOOT_ONCE.hasTag(spec.getVmInventory().getUuid(), VmInstanceVO.class)) { + VmSystemTags.BOOT_ORDER.delete(spec.getVmInventory().getUuid()); + VmSystemTags.CDROM_BOOT_ONCE.delete(spec.getVmInventory().getUuid()); + } + + final String machineType = spec.getOsSpec().getMachineType(); + if (StringUtils.isEmpty(machineType)) { + VmSystemTags.MACHINE_TYPE.delete(spec.getVmInventory().getUuid()); + } else { + SystemTagCreator creator = VmSystemTags.MACHINE_TYPE.newSystemTagCreator(spec.getVmInventory().getUuid()); + creator.setTagByTokens(map(e(VmSystemTags.MACHINE_TYPE_TOKEN, machineType))); + creator.inherent = false; + creator.recreate = true; + creator.create(); + } + } + + @Override + public void startVmOnKvmFailed(KVMHostInventory host, VmInstanceSpec spec, ErrorCode err) { + + } + + @Override + public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAgentCommands.StartVmCmd cmd) { + + } + + @Override + public void afterReceiveVmDeviceInfoResponse(VmInstanceInventory vm, KVMAgentCommands.VmDevicesInfoResponse rsp, VmInstanceSpec spec) { + saveVmEdkStatesFromCommand(spec.getVmInventory().getUuid(), rsp); + } + + @SuppressWarnings("unchecked") + private void saveVmEdkStatesFromCommand(String vmUuid, KVMAgentCommands.VmDevicesInfoResponse rsp) { + if (StringUtils.isEmpty(rsp.getEdkRpm())) { + VM_EDK.deleteInherentTag(vmUuid); + return; + } + + SystemTagCreator creator = VM_EDK.newSystemTagCreator(vmUuid); + creator.setTagByTokens(map(e(EDK_RPM_TOKEN, rsp.getEdkRpm()))); + creator.inherent = true; + creator.recreate = true; + creator.create(); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/BootOrderKvmStartVmExtension.java b/plugin/kvm/src/main/java/org/zstack/kvm/BootOrderKvmStartVmExtension.java deleted file mode 100644 index d8896ae447b..00000000000 --- a/plugin/kvm/src/main/java/org/zstack/kvm/BootOrderKvmStartVmExtension.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.zstack.kvm; - -import org.zstack.compute.vm.VmSystemTags; -import org.zstack.header.errorcode.ErrorCode; -import org.zstack.header.vm.VmInstanceSpec; -import org.zstack.header.vm.VmInstanceVO; - -/** - * author:kaicai.hu - * Date:2019/12/25 - */ -public class BootOrderKvmStartVmExtension implements KVMStartVmExtensionPoint { - - @Override - public void startVmOnKvmSuccess(KVMHostInventory host, VmInstanceSpec spec) { - if (VmSystemTags.BOOT_ORDER_ONCE.hasTag(spec.getVmInventory().getUuid(), VmInstanceVO.class)) { - VmSystemTags.BOOT_ORDER.delete(spec.getVmInventory().getUuid()); - VmSystemTags.BOOT_ORDER_ONCE.delete(spec.getVmInventory().getUuid()); - } - if (VmSystemTags.CDROM_BOOT_ONCE.hasTag(spec.getVmInventory().getUuid(), VmInstanceVO.class)) { - VmSystemTags.BOOT_ORDER.delete(spec.getVmInventory().getUuid()); - VmSystemTags.CDROM_BOOT_ONCE.delete(spec.getVmInventory().getUuid()); - } - } - - @Override - public void startVmOnKvmFailed(KVMHostInventory host, VmInstanceSpec spec, ErrorCode err) { - - } - - @Override - public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAgentCommands.StartVmCmd cmd) { - - } -} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/HostKeyIdentityHelper.java b/plugin/kvm/src/main/java/org/zstack/kvm/HostKeyIdentityHelper.java new file mode 100644 index 00000000000..d100dbeee17 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/HostKeyIdentityHelper.java @@ -0,0 +1,138 @@ +package org.zstack.kvm; + +import org.apache.commons.lang.StringUtils; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.SimpleQuery; +import org.zstack.core.db.SimpleQuery.Op; +import org.zstack.header.host.HostKeyIdentityVO; +import org.zstack.header.host.HostKeyIdentityVO_; +import org.zstack.header.secret.SecretHostDefineReply; +import org.zstack.utils.ExceptionDSL; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.EntityExistsException; +import javax.persistence.PersistenceException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.SQLIntegrityConstraintViolationException; +import java.util.Base64; + +/** + * Shared helper for host envelope key identity: fingerprint and save/update. + * Used by KVMHost (zstack) and KVMEnvelopeKeySyncExtension (premium). + */ +public final class HostKeyIdentityHelper { + private static final CLogger logger = org.zstack.utils.logging.CLoggerImpl.getLogger(HostKeyIdentityHelper.class); + + private HostKeyIdentityHelper() { + } + + /** + * Compute fingerprint from public key (base64): SHA-256 of decoded key bytes, hex-encoded. + * Returns empty string if key is invalid or hashing fails. + */ + public static String fingerprintFromPublicKey(String publicKeyBase64) { + if (publicKeyBase64 == null || publicKeyBase64.isEmpty()) { + return ""; + } + try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64.trim()); + if (keyBytes.length == 0) { + return ""; + } + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest(keyBytes); + StringBuilder sb = new StringBuilder(hash.length * 2); + for (byte b : hash) { + sb.append(String.format("%02x", b & 0xff)); + } + return sb.toString(); + } catch (IllegalArgumentException | NoSuchAlgorithmException e) { + return ""; + } + } + + public static HostKeyIdentityVO getHostKeyIdentity(DatabaseFacade dbf, String hostUuid) { + SimpleQuery q = dbf.createQuery(HostKeyIdentityVO.class); + q.add(HostKeyIdentityVO_.hostUuid, Op.EQ, hostUuid); + return q.find(); + } + + /** + * Save or update host key identity. Validates publicKey (base64, 32 bytes, non-empty fingerprint). + * On invalid key, marks existing record as verified=false and returns without persisting bad data. + */ + public static void saveOrUpdateHostKeyIdentity(DatabaseFacade dbf, String hostUuid, String publicKey, boolean verified) { + if (StringUtils.isBlank(publicKey)) { + return; + } + + String keyToSave = publicKey.trim(); + byte[] decodedKey; + try { + decodedKey = Base64.getDecoder().decode(keyToSave); + } catch (IllegalArgumentException e) { + logger.warn("host " + hostUuid + " returned an invalid envelope public key"); + setVerified(dbf, hostUuid, false); + return; + } + if (decodedKey.length != 32) { + logger.warn("host " + hostUuid + " returned an envelope public key with unexpected length: " + decodedKey.length); + setVerified(dbf, hostUuid, false); + return; + } + String fingerprint = fingerprintFromPublicKey(keyToSave); + if (StringUtils.isBlank(fingerprint)) { + logger.warn("host " + hostUuid + " returned an envelope public key with empty fingerprint"); + setVerified(dbf, hostUuid, false); + return; + } + + HostKeyIdentityVO vo = getHostKeyIdentity(dbf, hostUuid); + if (vo == null) { + vo = new HostKeyIdentityVO(); + vo.setHostUuid(hostUuid); + vo.setPublicKey(keyToSave); + vo.setFingerprint(fingerprint); + vo.setVerified(verified); + vo.setCreateDate(new java.sql.Timestamp(System.currentTimeMillis())); + try { + dbf.persist(vo); + return; + } catch (PersistenceException e) { + if (!ExceptionDSL.isCausedBy(e, EntityExistsException.class) + && !ExceptionDSL.isCausedBy(e, SQLIntegrityConstraintViolationException.class, "Duplicate entry")) { + throw e; + } + vo = getHostKeyIdentity(dbf, hostUuid); + if (vo == null) { + throw e; + } + } + } + vo.setPublicKey(keyToSave); + vo.setFingerprint(fingerprint); + vo.setVerified(verified); + dbf.update(vo); + } + + /** + * Set verified flag for existing host key identity record, if present. + */ + public static void setVerified(DatabaseFacade dbf, String hostUuid, boolean verified) { + HostKeyIdentityVO vo = getHostKeyIdentity(dbf, hostUuid); + if (vo != null) { + vo.setVerified(verified); + dbf.update(vo); + } + } + + /** + * Whether the verify response error code indicates that key rotate/re-fetch is needed. + */ + public static boolean isRotateNeededGetError(String errorCode) { + if (errorCode == null) return false; + return SecretHostDefineReply.ERROR_CODE_KEYS_NOT_ON_DISK.equals(errorCode) + || SecretHostDefineReply.ERROR_CODE_KEY_FILES_INTEGRITY_MISMATCH.equals(errorCode); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/HostSecretEnvelopeCryptoExtensionPoint.java b/plugin/kvm/src/main/java/org/zstack/kvm/HostSecretEnvelopeCryptoExtensionPoint.java new file mode 100644 index 00000000000..252bffbf055 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/HostSecretEnvelopeCryptoExtensionPoint.java @@ -0,0 +1,13 @@ +package org.zstack.kvm; + +/** + * Extension point for sealing plaintext (e.g. DEK) with recipient's X25519 public key for host secret (e.g. vTPM). + * Implementation is provided by premium/crypto when available; KVM uses PluginRegistry.getExtensionList() to get it. + */ +public interface HostSecretEnvelopeCryptoExtensionPoint { + /** + * Seal plaintext with recipient's X25519 public key (raw 32 bytes). + * @return envelope = enc (32 bytes) || ciphertext (AEAD output), compatible with key-agent open. + */ + byte[] seal(byte[] recipientPublicKey, byte[] plaintext) throws Exception; +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 92e76ede2c5..5b675f4ff72 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -11,8 +11,10 @@ import org.zstack.header.host.VmNicRedirectConfig; import org.zstack.header.log.NoLogging; import org.zstack.header.vm.*; +import org.zstack.header.vm.additions.VmHostFileBackupJob; import org.zstack.header.vm.devices.DeviceAddress; import org.zstack.header.vm.devices.VirtualDeviceInfo; +import org.zstack.kvm.tpm.TpmTO; import org.zstack.network.securitygroup.RuleTO; import org.zstack.network.securitygroup.SecurityGroupMembersTO; import org.zstack.network.securitygroup.VmNicSecurityTO; @@ -24,6 +26,7 @@ import static org.zstack.utils.CollectionDSL.e; import static org.zstack.utils.CollectionDSL.map; +import static org.zstack.utils.CollectionUtils.transform; import static org.zstack.utils.opaque.OpaqueConstants.OPAQUE_KEY_RESPONSE_ERROR; public class KVMAgentCommands { @@ -369,6 +372,211 @@ public void setQemuVersion(String qemuVersion) { } } + public static class CreatePublicKeyCmd extends AgentCommand { + } + + public static class CreatePublicKeyResponse extends AgentResponse { + } + + public static class GetPublicKeyCmd extends AgentCommand { + } + + public static class GetPublicKeyResponse extends AgentResponse { + private String publicKey; + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + } + + public static class RotatePublicKeyCmd extends AgentCommand { + } + + public static class RotatePublicKeyResponse extends AgentResponse { + } + + public static class VerifyPublicKeyCmd extends AgentCommand { + } + + public static class VerifyPublicKeyResponse extends AgentResponse { + } + + public static class SecretHostDefineCmd extends AgentCommand { + /** Base64 envelope of DEK; agent expects this field name (encryptedDek). */ + private String encryptedDek; + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + private String secretUuid; + private String description; + + public String getEncryptedDek() { + return encryptedDek; + } + + public void setEncryptedDek(String encryptedDek) { + this.encryptedDek = encryptedDek; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + public static class SecretHostDefineResponse extends AgentResponse { + private String secretUuid; + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + } + + public static class SecretHostGetCmd extends AgentCommand { + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } + } + + public static class SecretHostGetResponse extends AgentResponse { + private String secretUuid; + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + } + + public static class SecretHostDeleteCmd extends AgentCommand { + private String vmUuid; + private String purpose; + private Integer keyVersion; + private String usageInstance; + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public String getUsageInstance() { + return usageInstance; + } + + public void setUsageInstance(String usageInstance) { + this.usageInstance = usageInstance; + } + } + + public static class SecretHostDeleteResponse extends AgentResponse { + } + public static class PingCmd extends AgentCommand { public String hostUuid; public Map configs; @@ -2067,7 +2275,9 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd { private List cdRoms = new ArrayList<>(); private List dataVolumes; private List cacheVolumes; + private VolumeTO nvRam; private List nics; + private TpmTO tpm; private long timeout; private Map addons; private boolean instanceOfferingOnlineChange; @@ -2102,6 +2312,8 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd { private String bootMode; // used when bootMode == 'UEFI' private boolean secureBoot; + private String edkVersion; + private boolean fromForeignHypervisor; private String machineType; private Integer pciePortNums; @@ -2258,6 +2470,14 @@ public void setSecureBoot(boolean secureBoot) { this.secureBoot = secureBoot; } + public String getEdkVersion() { + return edkVersion; + } + + public void setEdkVersion(String edkVersion) { + this.edkVersion = edkVersion; + } + public boolean isEmulateHyperV() { return emulateHyperV; } @@ -2537,6 +2757,14 @@ public void setCacheVolumes(List cacheVolumes) { this.cacheVolumes = cacheVolumes; } + public VolumeTO getNvRam() { + return nvRam; + } + + public void setNvRam(VolumeTO nvRam) { + this.nvRam = nvRam; + } + public List getNics() { return nics; } @@ -2545,6 +2773,14 @@ public void setNics(List nics) { this.nics = nics; } + public TpmTO getTpm() { + return tpm; + } + + public void setTpm(TpmTO tpm) { + this.tpm = tpm; + } + public long getTimeout() { return timeout; } @@ -2698,11 +2934,86 @@ public void setVmCpuVendorId(String vmCpuVendorId) { public static class StartVmResponse extends VmDevicesInfoResponse { } + public static class VmHostFileTO { + private String path; + /** + * maybe "NvRam" or "TpmState" ... + * @see org.zstack.header.vm.additions.VmHostFileType + */ + private String type; + /** + * file format: Raw or TarballGzip + * @see org.zstack.header.vm.additions.VmHostFileContentFormat + */ + private String fileFormat; + /** + * operation: Write, Prepare, or Delete + * only use in WriteVmHostFileContentCmd + * @see org.zstack.header.vm.additions.VmHostFileOperation + */ + private String operation; + /** + * null if operation is Prepare or Delete + */ + @NoLogging(type = NoLogging.Type.LongText) + private String contentBase64; + private String error; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFileFormat() { + return fileFormat; + } + + public void setFileFormat(String fileFormat) { + this.fileFormat = fileFormat; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getContentBase64() { + return contentBase64; + } + + public void setContentBase64(String contentBase64) { + this.contentBase64 = contentBase64; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + } + public static class VmDevicesInfoResponse extends AgentResponse { private List nicInfos; private List virtualDeviceInfoList; private VirtualDeviceInfo memBalloonInfo; private VirtualizerInfoTO virtualizerInfo; + private String edkRpm; @NoLogging private String vmXml; @@ -2738,6 +3049,14 @@ public void setVirtualizerInfo(VirtualizerInfoTO virtualizerInfo) { this.virtualizerInfo = virtualizerInfo; } + public String getEdkRpm() { + return edkRpm; + } + + public void setEdkRpm(String edkRpm) { + this.edkRpm = edkRpm; + } + public String getVmXml() { return vmXml; } @@ -2762,6 +3081,67 @@ public void setVmInstanceUuid(String vmInstanceUuid) { public static class SyncVmDeviceInfoResponse extends VmDevicesInfoResponse { } + public static class ReadVmHostFileContentCmd extends AgentCommand { + /** + * without contentBase64, fileFormat + */ + private List hostFiles = new ArrayList<>(); + + public List getPaths() { + return transform(hostFiles, VmHostFileTO::getPath); + } + + public List getHostFiles() { + return hostFiles; + } + + public void setHostFiles(List hostFiles) { + this.hostFiles = hostFiles; + } + } + + public static class ReadVmHostFileContentResponse extends AgentResponse { + private List hostFiles = new ArrayList<>(); + + public List getHostFiles() { + return hostFiles; + } + + public void setHostFiles(List hostFiles) { + this.hostFiles = hostFiles; + } + } + + public static class WriteVmHostFileContentCmd extends AgentCommand { + private List hostFiles = new ArrayList<>(); + + public List getHostFiles() { + return hostFiles; + } + + public void setHostFiles(List hostFiles) { + this.hostFiles = hostFiles; + } + } + + public static class WriteVmHostFileContentResponse extends AgentResponse { + } + + public static class BackupVmHostFileCmd extends AgentCommand { + private List vmHostFileBackupJobs; + + public List getVmHostFileBackupJobs() { + return vmHostFileBackupJobs; + } + + public void setVmHostFileBackupJobs(List vmHostFileBackupJobs) { + this.vmHostFileBackupJobs = vmHostFileBackupJobs; + } + } + + public static class BackupVmHostFileResponse extends AgentResponse { + } + public static class VmNicInfo { private String macAddress; private DeviceAddress deviceAddress; @@ -4491,6 +4871,7 @@ public void setVmUuid(String vmUuid) { } public static class TakeVmConsoleScreenshotRsp extends AgentResponse { + @NoLogging(type = NoLogging.Type.LongText) private String imageData; public String getImageData() { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index 7cd78c36c93..05142b6db6b 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -1,7 +1,9 @@ package org.zstack.kvm; import org.zstack.header.configuration.PythonClass; +import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.additions.VmHostFileType; @PythonClass public interface KVMConstant { @@ -85,6 +87,9 @@ public interface KVMConstant { String KVM_REGISTER_PRIMARY_VM_HEARTBEAT = "/register/primary/vm/heartbeat"; String CLEAN_FIRMWARE_FLASH = "/clean/firmware/flash"; String FSTRIM_VM_PATH = "/vm/fstrim"; + String READ_VM_HOST_FILE_PATH = "/vm/hostfile/read"; + String WRITE_VM_HOST_FILE_PATH = "/vm/hostfile/write"; + String BACKUP_VM_HOST_FILE_PATH = "/vm/hostfile/backup"; String ISO_TO = "kvm.isoto"; String ANSIBLE_PLAYBOOK_NAME = "kvm.py"; @@ -120,6 +125,21 @@ public interface KVMConstant { String KVM_UPDATE_HOST_NQN_PATH = "/host/nqn/update"; String KVM_UPDATE_HOSTNAME_PATH = "/host/hostname/update"; + String KVM_CREATE_ENVELOPE_KEY_PATH = "/host/key/envelope/createEnvelopeKey"; + String KVM_GET_ENVELOPE_KEY_PATH = "/host/key/envelope/getEnvelopePublicKey"; + String KVM_ROTATE_ENVELOPE_KEY_PATH = "/host/key/envelope/rotateEnvelopeKey"; + String KVM_VERIFY_ENVELOPE_KEY_PATH = "/host/key/envelope/checkEnvelopeKey"; + String KVM_GET_SECRET_PATH = "/host/key/envelope/getSecret"; + String KVM_ENSURE_SECRET_PATH = "/host/key/envelope/ensureSecret"; + String KVM_DELETE_SECRET_PATH = "/host/key/envelope/deleteSecret"; + + /** HTTP timeout in seconds for envelope key sync (verify/create/rotate/get) to agent. */ + long ENVELOPE_KEY_HTTP_TIMEOUT_SEC = 5L; + + /** Max size in bytes for DEK payload in SecretHostDefine (decoded from dekBase64). */ + int MAX_DEK_BYTES = 1024; + String HOST_SECRET_USAGE_INSTANCE_VTPM = "tpm0"; + String KVM_HOST_FILE_DOWNLOAD_PATH = "/host/file/download"; String KVM_HOST_FILE_UPLOAD_PATH = "/host/file/upload"; String KVM_HOST_FILE_DOWNLOAD_PROGRESS_PATH = "/host/file/progress"; @@ -183,6 +203,45 @@ public interface KVMConstant { public static final String L2_PROVIDER_TYPE_OVS_DPDK = "OvsDpdk"; public static final String L2_PROVIDER_TYPE_MACVLAN = "MacVlan"; + public static final String EDK_VERSION_NONE = "None"; + public static final String NV_RAM_FILE_PATH_FORMAT = "/var/lib/libvirt/qemu/nvram/%s-host-files/%s.fd"; + public static String buildNvramFilePath(String vmUuid) { + return String.format(NV_RAM_FILE_PATH_FORMAT, vmUuid, vmUuid); + } + + public static final String TPM_STATE_FILE_PATH_FORMAT = "/var/lib/libvirt/swtpm/%s/"; + public static String buildTpmStateFilePath(String vmUuid) { + String vmUuidWithHyphen = vmUuid.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); + return String.format(TPM_STATE_FILE_PATH_FORMAT, vmUuidWithHyphen); + } + + public static String buildPathForVmHostFileType(VmHostFileType type, String vmUuid) { + switch (type) { + case NvRam: return buildNvramFilePath(vmUuid); + case TpmState: return buildTpmStateFilePath(vmUuid); + default: throw new CloudRuntimeException("unsupported VmHostFileType: " + type); + } + } + + public static final String NV_RAM_SNAPSHOT_BACKUP_FILE_PATH_FORMAT = "/var/lib/libvirt/qemu/nvram/%s-host-files/%s.fd.snapshot-backup"; + public static String buildNvramSnapshotBackupFilePath(String vmUuid) { + return String.format(NV_RAM_SNAPSHOT_BACKUP_FILE_PATH_FORMAT, vmUuid, vmUuid); + } + + public static final String TPM_STATE_SNAPSHOT_BACKUP_FILE_PATH_FORMAT = "/var/lib/libvirt/swtpm/%s.snapshot-backup/"; + public static String buildTpmStateSnapshotBackupFilePath(String vmUuid) { + String vmUuidWithHyphen = vmUuid.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); + return String.format(TPM_STATE_SNAPSHOT_BACKUP_FILE_PATH_FORMAT, vmUuidWithHyphen); + } + + public static String buildSnapshotBackupPathForVmHostFileType(VmHostFileType type, String vmUuid) { + switch (type) { + case NvRam: return buildNvramSnapshotBackupFilePath(vmUuid); + case TpmState: return buildTpmStateSnapshotBackupFilePath(vmUuid); + default: throw new CloudRuntimeException("unsupported VmHostFileType: " + type); + } + } + public static final String DHCP_BIN_FILE_PATH = "/usr/local/zstack/dnsmasq"; enum KvmVmState { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java index 6d1a7eaeb5c..4c2ff3a9175 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java @@ -11,6 +11,8 @@ import org.zstack.header.host.HostVO; import org.zstack.header.zone.ZoneVO; +import static org.zstack.kvm.KVMConstant.EDK_VERSION_NONE; + /** */ @GlobalConfigDefinition @@ -159,4 +161,19 @@ public class KVMGlobalConfig { type = Long.class ) public static GlobalConfig KVMAGENT_PHYSICAL_MEMORY_USAGE_HARD_LIMIT = new GlobalConfig(CATEGORY, "kvmagent.physicalmemory.usage.hardlimit"); + + @GlobalConfigDef(defaultValue = EDK_VERSION_NONE, + description = "Specify the EDK version to be used for the next VM startup. None indicates the use of the system's default EDK version") + @BindResourceConfig(value = {VmInstanceVO.class, ClusterVO.class}) + public static GlobalConfig VM_EDK_VERSION_CONFIG = new GlobalConfig(CATEGORY, "vm.edk.version"); + + @GlobalConfigValidation(numberGreaterThan = 1, numberLessThan = 86400) + @GlobalConfigDef(defaultValue = "15", type = Long.class, + description = "Interval in seconds for checking VM host files (NvRam, TpmState) on KVM hosts") + public static GlobalConfig VM_HOST_FILE_SYNC_INTERVAL = new GlobalConfig(CATEGORY, "vm.host.file.sync.interval"); + + @GlobalConfigValidation(numberGreaterThan = 1, numberLessThan = 30) + @GlobalConfigDef(defaultValue = "5", type = Integer.class, + description = "The concurrency level for syncing VM host files from KVM hosts") + public static GlobalConfig VM_HOST_FILE_SYNC_CONCURRENCY = new GlobalConfig(CATEGORY, "vm.host.file.sync.concurrency"); } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 86444cde9a6..9b699c62aac 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -55,6 +55,12 @@ import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.*; import org.zstack.header.host.MigrateVmOnHypervisorMsg.StorageMigrationPolicy; +import org.zstack.header.secret.SecretHostDefineMsg; +import org.zstack.header.secret.SecretHostDefineReply; +import org.zstack.header.secret.SecretHostDeleteMsg; +import org.zstack.header.secret.SecretHostDeleteReply; +import org.zstack.header.secret.SecretHostGetMsg; +import org.zstack.header.secret.SecretHostGetReply; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; @@ -230,6 +236,8 @@ public class KVMHost extends HostBase implements Host { private String fileDownloadPath; private String fileUploadPath; private String fileDownloadProgressPath; + private String readVmHostFilePath; + private String writeVmHostFilePath; public KVMHost(KVMHostVO self, KVMHostContext context) { super(self); @@ -480,6 +488,14 @@ public KVMHost(KVMHostVO self, KVMHostContext context) { ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PROGRESS_PATH); fileDownloadProgressPath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.READ_VM_HOST_FILE_PATH); + readVmHostFilePath = ub.build().toString(); + + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.WRITE_VM_HOST_FILE_PATH); + writeVmHostFilePath = ub.build().toString(); } static { @@ -738,6 +754,12 @@ protected void handleLocalMessage(Message msg) { handle((GetFileDownloadProgressMsg) msg); } else if (msg instanceof RestartKvmAgentMsg) { handle((RestartKvmAgentMsg) msg); + } else if (msg instanceof SecretHostGetMsg) { + handle((SecretHostGetMsg) msg); + } else if (msg instanceof SecretHostDefineMsg) { + handle((SecretHostDefineMsg) msg); + } else if (msg instanceof SecretHostDeleteMsg) { + handle((SecretHostDeleteMsg) msg); } else { super.handleLocalMessage(msg); } @@ -4422,9 +4444,15 @@ protected void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, fi cmd.setChassisAssetTag(NetworkGlobalProperty.CHASSIS_ASSET_TAG); } - String machineType = VmSystemTags.MACHINE_TYPE.getTokenByResourceUuid(cmd.getVmInstanceUuid(), - VmInstanceVO.class, VmSystemTags.MACHINE_TYPE_TOKEN); - cmd.setMachineType(StringUtils.isNotEmpty(machineType) ? machineType : "pc"); + if (spec.getOsSpec().getMachineType() == null) { + String machineType = VmSystemTags.MACHINE_TYPE.getTokenByResourceUuid(cmd.getVmInstanceUuid(), + VmInstanceVO.class, VmSystemTags.MACHINE_TYPE_TOKEN); + cmd.setMachineType(StringUtils.isNotEmpty(machineType) ? machineType : "pc"); + spec.getOsSpec().setMachineType(cmd.getMachineType()); + } else { + cmd.setMachineType(spec.getOsSpec().getMachineType()); + } + String machineType = cmd.getMachineType(); if (KVMSystemTags.VM_PREDEFINED_PCI_BRIDGE_NUM.hasTag(spec.getVmInventory().getUuid())) { cmd.setPredefinedPciBridgeNum(Integer.valueOf(KVMSystemTags.VM_PREDEFINED_PCI_BRIDGE_NUM.getTokenByResourceUuid(spec.getVmInventory().getUuid(), KVMSystemTags.VM_PREDEFINED_PCI_BRIDGE_NUM_TOKEN))); @@ -4519,10 +4547,6 @@ protected void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, fi String bootMode = VmSystemTags.BOOT_MODE.getTokenByResourceUuid(spec.getVmInventory().getUuid(), VmSystemTags.BOOT_MODE_TOKEN); cmd.setBootMode(bootMode == null ? ImageBootMode.Legacy.toString() : bootMode); - if (cmd.getBootMode().equals(ImageBootMode.UEFI.toString()) - || cmd.getBootMode().equals(ImageBootMode.UEFI_WITH_CSM.toString())) { - cmd.setSecureBoot(VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT.value(Boolean.class)); - } deviceBootOrderOperator.updateVmDeviceBootOrder(cmd, spec); cmd.setBootDev(toKvmBootDev(spec.getBootOrders())); @@ -5294,6 +5318,250 @@ public void handle(ErrorCode errCode, Map data) { }).start(); } + private void handle(SecretHostGetMsg msg) { + SecretHostGetReply reply = new SecretHostGetReply(); + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + msg.getKeyVersion() == null || StringUtils.isBlank(msg.getUsageInstance())) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for get secret")); + bus.reply(msg, reply); + return; + } + + String url = buildUrl(KVMConstant.KVM_GET_SECRET_PATH); + KVMAgentCommands.SecretHostGetCmd cmd = new KVMAgentCommands.SecretHostGetCmd(); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setUsageInstance(msg.getUsageInstance()); + Map headers = new HashMap<>(); + headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); + Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostGetResponse.class); + http.runBeforeAsyncJsonPostExts(headers); + restf.asyncJsonPost(url, http.commandStr, headers, new JsonAsyncRESTCallback(msg, reply) { + @Override + public void fail(ErrorCode err) { + reply.setError(err != null ? err : operr("get secret on agent failed")); + bus.reply(msg, reply); + } + + @Override + public void success(KVMAgentCommands.SecretHostGetResponse rsp) { + if (rsp != null && rsp.isSuccess() && StringUtils.isNotBlank(rsp.getSecretUuid())) { + reply.setSecretUuid(rsp.getSecretUuid()); + } else if (rsp != null && rsp.isSuccess()) { + ErrorCode err = new ErrorCode(); + err.setCode(SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND); + err.setDetails("get secret succeeded but secretUuid is empty"); + reply.setError(err); + } else { + reply.setError(buildSecretAgentError(rsp, "get secret failed")); + } + bus.reply(msg, reply); + } + + @Override + public Class getReturnClass() { + return KVMAgentCommands.SecretHostGetResponse.class; + } + }, TimeUnit.SECONDS, KVMConstant.ENVELOPE_KEY_HTTP_TIMEOUT_SEC); + } + + private void handle(SecretHostDefineMsg msg) { + SecretHostDefineReply reply = new SecretHostDefineReply(); + if (org.apache.commons.lang.StringUtils.isBlank(msg.getDekBase64())) { + reply.setError(operr("dekBase64 is required")); + bus.reply(msg, reply); + return; + } + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + msg.getKeyVersion() == null || StringUtils.isBlank(msg.getUsageInstance())) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for ensure secret")); + bus.reply(msg, reply); + return; + } + String hostUuid = getSelf().getUuid(); + HostKeyIdentityVO identity = HostKeyIdentityHelper.getHostKeyIdentity(dbf, hostUuid); + String pubKey = identity != null ? org.apache.commons.lang.StringUtils.trimToNull(identity.getPublicKey()) : null; + Boolean verifyOk = identity != null ? identity.getVerified() : null; + if (pubKey == null) { + reply.setError(operr("no public key for host, connect/reconnect did not sync key")); + bus.reply(msg, reply); + return; + } + String storedFingerprint = StringUtils.trimToNull(identity.getFingerprint()); + String computed = HostKeyIdentityHelper.fingerprintFromPublicKey(pubKey); + if (storedFingerprint == null || !StringUtils.equals(storedFingerprint, computed)) { + reply.setError(operr("host public key fingerprint mismatch, key may be corrupted or tampered")); + bus.reply(msg, reply); + return; + } + if (!Boolean.TRUE.equals(verifyOk)) { + reply.setError(operr("host secret key verify not ok, not synced")); + bus.reply(msg, reply); + return; + } + byte[] dekRaw; + try { + dekRaw = java.util.Base64.getDecoder().decode(msg.getDekBase64().trim()); + } catch (IllegalArgumentException e) { + reply.setError(operr("invalid dekBase64: %s", e.getMessage())); + bus.reply(msg, reply); + return; + } + if (dekRaw == null || dekRaw.length == 0) { + reply.setError(operr("dekBase64 decoded to empty")); + bus.reply(msg, reply); + return; + } + + if (dekRaw.length > KVMConstant.MAX_DEK_BYTES) { + reply.setError(operr("dekBase64 decoded payload is too large")); + bus.reply(msg, reply); + return; + } + + byte[] pubKeyBytes; + try { + pubKeyBytes = java.util.Base64.getDecoder().decode(pubKey); + } catch (IllegalArgumentException e) { + reply.setError(operr("invalid host public key in DB: %s", e.getMessage())); + bus.reply(msg, reply); + return; + } + if (pubKeyBytes == null || pubKeyBytes.length != 32) { + reply.setError(operr("host public key must be 32 bytes (X25519)")); + bus.reply(msg, reply); + return; + } + java.util.List sealers = pluginRegistry.getExtensionList(HostSecretEnvelopeCryptoExtensionPoint.class); + if (sealers == null || sealers.isEmpty()) { + reply.setError(operr("host secret envelope sealer not available (premium crypto module required)")); + bus.reply(msg, reply); + return; + } + byte[] envelope; + try { + envelope = sealers.get(0).seal(pubKeyBytes, dekRaw); + } catch (Exception e) { + reply.setError(operr("HPKE seal failed: %s", e.getMessage())); + bus.reply(msg, reply); + return; + } + String envelopeDekBase64 = java.util.Base64.getEncoder().encodeToString(envelope); + String url = buildUrl(KVMConstant.KVM_ENSURE_SECRET_PATH); + KVMAgentCommands.SecretHostDefineCmd cmd = new KVMAgentCommands.SecretHostDefineCmd(); + cmd.setEncryptedDek(envelopeDekBase64); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setSecretUuid(msg.getSecretUuid()); + cmd.setDescription(msg.getDescription() != null ? msg.getDescription() : ""); + cmd.setUsageInstance(msg.getUsageInstance()); + Map headers = new HashMap<>(); + headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); + Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostDefineResponse.class); + http.runBeforeAsyncJsonPostExts(headers); + restf.asyncJsonPost(url, http.commandStr, headers, new JsonAsyncRESTCallback(msg, reply) { + @Override + public void fail(ErrorCode err) { + reply.setError(err != null ? err : operr("ensure secret on agent failed")); + bus.reply(msg, reply); + } + + @Override + public void success(KVMAgentCommands.SecretHostDefineResponse rsp) { + if (rsp != null && rsp.isSuccess()) { + if (rsp.getSecretUuid() != null) { + reply.setSecretUuid(rsp.getSecretUuid()); + } + } else { + reply.setError(buildSecretAgentError(rsp, "ensure secret failed")); + } + bus.reply(msg, reply); + } + + @Override + public Class getReturnClass() { + return KVMAgentCommands.SecretHostDefineResponse.class; + } + }, TimeUnit.SECONDS, KVMConstant.ENVELOPE_KEY_HTTP_TIMEOUT_SEC); + } + + private void handle(SecretHostDeleteMsg msg) { + SecretHostDeleteReply reply = new SecretHostDeleteReply(); + if (StringUtils.isBlank(msg.getVmUuid()) || StringUtils.isBlank(msg.getPurpose()) || + StringUtils.isBlank(msg.getUsageInstance()) || msg.getKeyVersion() == null) { + reply.setError(operr("vmUuid, purpose, keyVersion and usageInstance are required for delete secret")); + bus.reply(msg, reply); + return; + } + + String url = buildUrl(KVMConstant.KVM_DELETE_SECRET_PATH); + KVMAgentCommands.SecretHostDeleteCmd cmd = new KVMAgentCommands.SecretHostDeleteCmd(); + cmd.setVmUuid(msg.getVmUuid()); + cmd.setPurpose(msg.getPurpose()); + cmd.setKeyVersion(msg.getKeyVersion()); + cmd.setUsageInstance(msg.getUsageInstance()); + Map headers = new HashMap<>(); + headers.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, getSelf().getUuid()); + Http http = new Http<>(url, cmd, KVMAgentCommands.SecretHostDeleteResponse.class); + http.runBeforeAsyncJsonPostExts(headers); + restf.asyncJsonPost(url, http.commandStr, headers, new JsonAsyncRESTCallback(msg, reply) { + @Override + public void fail(ErrorCode err) { + reply.setError(err != null ? err : operr("delete secret on agent failed")); + bus.reply(msg, reply); + } + + @Override + public void success(KVMAgentCommands.SecretHostDeleteResponse rsp) { + if (rsp == null || !rsp.isSuccess()) { + ErrorCode agentErr = buildSecretAgentError(rsp, "delete secret failed"); + if (!SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND.equals(agentErr.getCode())) { + reply.setError(agentErr); + } + } + bus.reply(msg, reply); + } + + @Override + public Class getReturnClass() { + return KVMAgentCommands.SecretHostDeleteResponse.class; + } + }, TimeUnit.SECONDS, KVMConstant.ENVELOPE_KEY_HTTP_TIMEOUT_SEC); + } + + private ErrorCode buildSecretAgentError(KVMAgentCommands.AgentResponse rsp, String defaultMessage) { + String raw = rsp != null ? StringUtils.trimToNull(rsp.getError()) : null; + if (raw == null) { + return operr(defaultMessage); + } + String stable = stableKeyAgentErrorCodeFromRawMessage(raw); + if (stable != null) { + ErrorCode err = new ErrorCode(); + err.setCode(stable); + err.setDetails(raw); + return err; + } + return operr("%s: %s", defaultMessage, raw); + } + + private static String stableKeyAgentErrorCodeFromRawMessage(String raw) { + String t = raw.trim(); + int colon = t.indexOf(':'); + String head = (colon >= 0 ? t.substring(0, colon) : t).trim(); + if (head.isEmpty()) { + return null; + } + if (SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND.equals(head)) { + return head; + } + if (head.startsWith("KEY_AGENT_")) { + return head; + } + return null; + } + @Override protected void deleteTakeOverFlag(Completion completion) { if (CoreGlobalProperty.UNIT_TEST_ON) { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java index f630fa79b39..709f069479f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java @@ -6,6 +6,7 @@ import org.zstack.header.tag.TagDefinition; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmNicVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO; import org.zstack.header.volume.VolumeVO; import org.zstack.tag.PatternedSystemTag; import org.zstack.tag.SystemTag; @@ -67,4 +68,16 @@ public class KVMSystemTags { public static SystemTag FORCE_DEPLOYMENT_ONCE = new SystemTag("force::deployment::once", HostVO.class); + + public static final String EDK_RPM_TOKEN = "edkRpm"; + public static PatternedSystemTag VM_EDK = + new PatternedSystemTag(String.format("vm::edk::{%s}", EDK_RPM_TOKEN), VmInstanceVO.class); + + public static final String SWTPM_VERSION_TOKEN = "version"; + public static PatternedSystemTag SWTPM_VERSION = + new PatternedSystemTag(String.format("swtpm::{%s}", SWTPM_VERSION_TOKEN), HostVO.class); + + public static final String TPM_KEY_PROVIDER_NAME_TOKEN = "keyProviderName"; + public static PatternedSystemTag TPM_KEY_PROVIDER_NAME = + new PatternedSystemTag(String.format("tpm::keyProvider::{%s}", TPM_KEY_PROVIDER_NAME_TOKEN), VmHostBackupFileVO.class); } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KvmCommandSender.java b/plugin/kvm/src/main/java/org/zstack/kvm/KvmCommandSender.java index 139f417c221..d8e711d6718 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KvmCommandSender.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KvmCommandSender.java @@ -69,6 +69,11 @@ public KvmCommandSender(String hostUuid, boolean noStatusCheck) { DebugUtils.Assert(hostUuid != null, "hostUuid cannot be null"); } + public KvmCommandSender disableHostStatusCheck() { + this.noStatusCheck = true; + return this; + } + public void send(final Object cmd, final String path, final KvmCommandFailureChecker checker, final SteppingSendCallback completion) { List msgs = hostUuids.stream().map(huuid -> { KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java new file mode 100644 index 00000000000..a30a6691f93 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootExtensions.java @@ -0,0 +1,793 @@ +package org.zstack.kvm.efi; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.core.Platform; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.db.SQLBatch; +import org.zstack.core.workflow.SimpleFlowChain; +import org.zstack.header.core.Completion; +import org.zstack.header.core.NoErrorCompletion; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.core.workflow.FlowDoneHandler; +import org.zstack.header.core.workflow.FlowErrorHandler; +import org.zstack.header.core.workflow.FlowTrigger; +import org.zstack.header.core.workflow.NoRollbackFlow; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.exception.CloudRuntimeException; +import org.zstack.header.message.MessageReply; +import org.zstack.header.vm.DiskAO; +import org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.AfterReimageVmInstanceExtensionPoint; +import org.zstack.header.vm.BeforeHaStartVmInstanceExtensionPoint; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceMigrateExtensionPoint; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstantiateResourceException; +import org.zstack.header.vm.VmJustBeforeDeleteFromDbExtensionPoint; +import org.zstack.header.vm.VmMigrationType; +import org.zstack.header.vm.VmPreMigrationExtensionPoint; +import org.zstack.header.vm.VmReleaseResourceExtensionPoint; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileContentVO; +import org.zstack.header.vm.additions.VmHostFileContentVO_; +import org.zstack.header.vm.additions.VmHostFileOperation; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.header.storage.snapshot.ConsistentType; +import org.zstack.header.storage.snapshot.CreateVolumesSnapshotOverlayInnerMsg; +import org.zstack.header.storage.snapshot.TakeVolumesSnapshotOnKvmReply; +import org.zstack.header.storage.snapshot.VolumeSnapshotCreationExtensionPoint; +import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupInventory; +import org.zstack.header.vm.devices.NvRamSpec; +import org.zstack.header.volume.VolumeInventory; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.kvm.KVMAgentCommands.*; +import org.zstack.kvm.KVMGlobalConfig; +import org.zstack.kvm.KVMHostInventory; +import org.zstack.kvm.KVMStartVmExtensionPoint; +import org.zstack.kvm.KvmCommandSender; +import org.zstack.kvm.KvmResponseWrapper; +import org.zstack.kvm.VolumeTO; +import org.zstack.kvm.vmfiles.message.SyncVmHostFilesFromHostMsg; +import org.zstack.resourceconfig.ResourceConfig; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.Tuple; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.zstack.core.Platform.operr; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.PostMigration; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.BeforeHaStart; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.PrepareReRead; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.PrepareRead; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.ResourceRelease; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.SnapshotGroupOnlineBackup; +import static org.zstack.header.vm.additions.VmHostFileType.NvRam; +import static org.zstack.kvm.KVMConstant.*; +import static org.zstack.utils.CollectionDSL.list; + +public class KvmSecureBootExtensions implements KVMStartVmExtensionPoint, + PreVmInstantiateResourceExtensionPoint, + VmJustBeforeDeleteFromDbExtensionPoint, + VmPreMigrationExtensionPoint, + AfterReimageVmInstanceExtensionPoint, + VmReleaseResourceExtensionPoint, + VmInstanceMigrateExtensionPoint, + VolumeSnapshotCreationExtensionPoint, + BeforeHaStartVmInstanceExtensionPoint { + private static final CLogger logger = Utils.getLogger(KvmSecureBootExtensions.class); + + @Autowired + private CloudBus bus; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private KvmVmHostFileFactory vmHostFileFactory; + + private final Object hostFileLock = new Object(); + + @Override + public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAgentCommands.StartVmCmd cmd) { + if (isUefiBootMode(cmd.getBootMode())) { + ResourceConfig resourceConfig; + resourceConfig = resourceConfigFacade.getResourceConfig(VmGlobalConfig.ENABLE_UEFI_SECURE_BOOT.getIdentity()); + cmd.setSecureBoot(resourceConfig.getResourceConfigValue(spec.getVmInventory().getUuid(), Boolean.class)); + + resourceConfig = resourceConfigFacade.getResourceConfig(KVMGlobalConfig.VM_EDK_VERSION_CONFIG.getIdentity()); + final String edkVersion = resourceConfig.getResourceConfigValue(spec.getVmInventory().getUuid(), String.class); + if (!Objects.equals(edkVersion, EDK_VERSION_NONE)) { + cmd.setEdkVersion(edkVersion); + } + } + + final NvRamSpec nvRam = spec.getDevicesSpec().getNvRam(); + if (nvRam != null && nvRam.isNeedRegister()) { + prepareNvRamToStartVmCmd(cmd, nvRam, host); + } + } + + private void prepareNvRamToStartVmCmd(KVMAgentCommands.StartVmCmd cmd, NvRamSpec nvRam, KVMHostInventory host) { + VolumeTO volume = new VolumeTO(); + volume.setDeviceType(VolumeTO.FILE); + volume.setInstallPath(buildNvramFilePath(cmd.getVmInstanceUuid())); + volume.setVolumeUuid(null); // not a volume + cmd.setNvRam(volume); + + synchronized (hostFileLock) { + final Timestamp now = Timestamp.from(Instant.now()); + VmHostFileVO nvRamFile = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, cmd.getVmInstanceUuid()) + .eq(VmHostFileVO_.type, NvRam) + .eq(VmHostFileVO_.hostUuid, host.getUuid()) + .find(); + if (nvRamFile == null) { + nvRamFile = new VmHostFileVO(); + nvRamFile.setUuid(Platform.getUuid()); + nvRamFile.setHostUuid(host.getUuid()); + nvRamFile.setVmInstanceUuid(cmd.getVmInstanceUuid()); + nvRamFile.setType(NvRam); + nvRamFile.setPath(volume.getInstallPath()); + nvRamFile.setCreateDate(now); + nvRamFile.setResourceName("NvRam file for " + cmd.getVmInstanceUuid()); + databaseFacade.persist(nvRamFile); + } else { + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, nvRamFile.getUuid()) + .set(VmHostFileVO_.path, volume.getInstallPath()) + .set(VmHostFileVO_.lastOpDate, now) + .update(); + } + } + } + + @Override + public void preMigrateVm(VmInstanceInventory inv, String destHostUuid, Completion completion) { + prepareNvRamBeforeMigration(inv, destHostUuid, completion); + } + + @Override + public void preVmMigration(VmInstanceInventory vm, VmMigrationType type, String dstHostUuid, Completion completion) { + completion.success(); // use preMigrateVm instead of preVmMigration to prevent from handle twice + } + + private void prepareNvRamBeforeMigration(VmInstanceInventory vm, String dstHostUuid, Completion completion) { + if (dstHostUuid == null) { + completion.success(); + return; + } + + boolean needRegisterNvRam = vmHostFileFactory.needRegister(NvRam, vm.getUuid()); + if (!needRegisterNvRam) { + completion.success(); + return; + } + + SimpleFlowChain.of("prepare-nvram-before-vm-" + vm.getUuid() + "-migrate") + .then("prepare-nvram-folder-on-dest-host", trigger -> { + VmHostFileTO to = new VmHostFileTO(); + to.setPath(buildNvramFilePath(vm.getUuid())); + to.setType(NvRam.toString()); + to.setOperation(VmHostFileOperation.Prepare.toString()); + + RewriteVmHostFilesContext context = new RewriteVmHostFilesContext(); + context.hostUuid = dstHostUuid; + context.hostFiles = list(to); + + rewriteVmHostFiles(context, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + }) + // TpmState folder is not needed to prepare + .propagateExceptionTo(completion) + .done(completion::success) + .error(completion::fail) + .start(); + } + + public static class RewriteVmHostFilesContext { + public String hostUuid; + public List hostFiles; + } + + public void rewriteVmHostFiles(RewriteVmHostFilesContext context, Completion completion) { + KvmCommandSender sender = new KvmCommandSender(context.hostUuid); + KVMAgentCommands.WriteVmHostFileContentCmd cmd = new KVMAgentCommands.WriteVmHostFileContentCmd(); + cmd.setHostFiles(context.hostFiles); + + sender.send(cmd, WRITE_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.WriteVmHostFileContentResponse writeRsp = wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); + return writeRsp.isSuccess() ? null : + operr("failed to write file content response").withException(writeRsp.getError()); + }, new ReturnValueCompletion(completion) { + @Override + public void success(KvmResponseWrapper wrapper) { + KVMAgentCommands.WriteVmHostFileContentResponse writeRsp = wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); + if (!writeRsp.isSuccess()) { + completion.fail(operr("failed to write file content response").withException(writeRsp.getError())); + return; + } + completion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + completion.fail(errorCode); + } + }); + } + + @Override + public void startVmOnKvmSuccess(KVMHostInventory host, VmInstanceSpec spec) { + // do-nothing + } + + @Override + public void startVmOnKvmFailed(KVMHostInventory host, VmInstanceSpec spec, ErrorCode err) { + // do-nothing + } + + private boolean isUefiBootMode(String bootMode) { + return VmTpmManager.isUefiBootMode(bootMode); + } + + @Override + public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException { + // do-nothing + } + + @Override + public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) { + final NvRamSpec nvRamSpec = spec.getDevicesSpec() == null ? null : spec.getDevicesSpec().getNvRam(); + if (nvRamSpec == null) { + completion.success(); + return; + } + + PrepareHostFileContext context = new PrepareHostFileContext(); + context.hostUuid = spec.getDestHost().getUuid(); + context.vmUuid = spec.getVmInventory().getUuid(); + context.type = NvRam; + context.backupUuid = nvRamSpec.getBackupFileUuid(); + context.syncReason = "pre-instantiate VM resource"; + prepareHostFileOnHost(context, completion); + } + + public static class PrepareHostFileContext { + public String hostUuid; + public String vmUuid; + public VmHostFileType type; + public String backupUuid; + public String syncReason; + + public String path; + // whether the NvRam is on the same host as before + private boolean sameHost = false; + private boolean writeSuccess = false; + private VmHostFileVO vmHostFile; + private VmHostBackupFileVO vmBackupFileVO; + + // property: VmHostBackupFileVO (when "backupUuid" is set) + // > VmHostFileVO (read success) + // > VmHostFileVO (read fail) + // > VmHostBackupFileVO (vmInstanceUuid matched) <- read this only if VmHostFileVO is not exist + } + + @SuppressWarnings("rawtypes") + public void prepareHostFileOnHost(PrepareHostFileContext context, Completion completion) { + SimpleFlowChain chain = new SimpleFlowChain(); + chain.setName("prepare-vm-host-file"); + chain.then(new NoRollbackFlow() { + String __name__ = "read-vm-host-file-from-origin-host"; + + @Override + public boolean skip(Map data) { + return StringUtils.isNotBlank(context.backupUuid); + } + + @Override + public void run(FlowTrigger trigger, Map data) { + VmHostFileVO vmHostFile = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.type, context.type) + .eq(VmHostFileVO_.vmInstanceUuid, context.vmUuid) + .orderByDesc(VmHostFileVO_.lastOpDate) + .limit(1) + .find(); + if (vmHostFile == null) { + logger.debug(String.format("skip to read/write %s host file for VM[vmUuid=%s]: file is not registered in MN", + context.type, context.vmUuid)); + trigger.next(); + return; + } + + context.sameHost = vmHostFile.getHostUuid().equals(context.hostUuid); + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(vmHostFile.getHostUuid()); + syncMsg.setVmUuid(context.vmUuid); + syncMsg.setSyncReason(PrepareRead.reason(context.syncReason)); + + if (vmHostFile.getType() == NvRam) { + context.path = vmHostFile.getPath(); + syncMsg.setNvRamPath(context.path); + } else if (vmHostFile.getType() == VmHostFileType.TpmState) { + context.path = vmHostFile.getPath(); + syncMsg.setTpmStateFolder(context.path); + } else { + throw new CloudRuntimeException("unsupported vm host file type: " + vmHostFile.getType()); + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + context.vmHostFile = vmHostFile; + } else { + logger.warn(String.format("failed to read vm host file for VM[vmUuid=%s] but still continue: %s", + context.vmUuid, reply.getError().getReadableDetails())); + } + trigger.next(); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "read-vm-host-file-from-backup"; + + @Override + public boolean skip(Map data) { + return context.vmHostFile != null; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + if (context.backupUuid == null) { + context.vmBackupFileVO = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.type, context.type) + .eq(VmHostBackupFileVO_.resourceUuid, context.vmUuid) + .orderByDesc(VmHostBackupFileVO_.lastOpDate) + .limit(1) + .find(); + } else { + context.vmBackupFileVO = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.uuid, context.backupUuid) + .find(); + if (context.vmBackupFileVO == null) { + trigger.fail(operr("cannot find matched vm-host backup file[backupUuid:%s]", + context.backupUuid)); + return; + } + } + if (context.vmBackupFileVO != null) { + logger.debug(String.format("use %s[type=%s] VM-host backup file for VM[uuid=%s]", + context.vmBackupFileVO.getUuid(), context.type, context.vmUuid)); + context.path = buildPathForVmHostFileType(context.type, context.vmUuid); + } + trigger.next(); + } + }).then(new NoRollbackFlow() { + String __name__ = "write-vm-host-file-to-dest-host"; + + @Override + public boolean skip(Map data) { + return (context.vmHostFile == null && context.vmBackupFileVO == null) + || (context.sameHost && context.vmHostFile != null); + } + + @Override + public void run(FlowTrigger trigger, Map data) { + String contentUuid = context.vmHostFile == null ? + context.vmBackupFileVO.getUuid() : context.vmHostFile.getUuid(); + VmHostFileContentVO content = Q.New(VmHostFileContentVO.class) + .eq(VmHostFileContentVO_.uuid, contentUuid) + .find(); + if (content == null) { + logger.debug(String.format("skip to write vm host file for VM[vmUuid=%s]: file content is not saved in MN", + context.vmUuid)); + trigger.next(); + return; + } + + VmHostFileTO to = new VmHostFileTO(); + to.setPath(context.path); + to.setType(context.type.toString()); + to.setFileFormat(content.getFormat().toString()); + to.setOperation(VmHostFileOperation.Write.toString()); + + String contentBase64 = Base64.getEncoder().encodeToString(content.getContent()); + to.setContentBase64(contentBase64); + + RewriteVmHostFilesContext rewriteContext = new RewriteVmHostFilesContext(); + rewriteContext.hostUuid = context.hostUuid; + rewriteContext.hostFiles = Collections.singletonList(to); + + rewriteVmHostFiles(rewriteContext, new Completion(trigger) { + @Override + public void success() { + context.writeSuccess = true; + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "re-read-vm-host-file-from-dest-host"; + + @Override + public boolean skip(Map data) { + return !context.writeSuccess; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(context.hostUuid); + syncMsg.setVmUuid(context.vmUuid); + syncMsg.setSyncReason(PrepareReRead.reason(context.syncReason)); + + if (context.type == NvRam) { + syncMsg.setNvRamPath(context.path); + } else if (context.type == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(context.path); + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + trigger.next(); + } else { + trigger.fail(reply.getError()); + } + } + }); + } + }).done(new FlowDoneHandler(completion) { + @Override + public void handle(Map data) { + completion.success(); + } + }).error(new FlowErrorHandler(completion) { + @Override + public void handle(ErrorCode errCode, Map data) { + completion.fail(errCode); + } + }).start(); + } + + @Override + public void preReleaseVmResource(VmInstanceSpec spec, Completion completion) { + completion.success(); + } + + static class NvRamVolumeContext { + String vmUuid; + DiskAO nvRamSpec; + VmInstanceSpec spec; + + VolumeInventory inventory; + } + + @Override + public void vmJustBeforeDeleteFromDb(VmInstanceInventory inv) { + String vmUuid = inv.getUuid(); + new SQLBatch() { + @Override + protected void scripts() { + sql(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .delete(); + sql(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) + .delete(); + } + }.execute(); + } + + @Override + public void afterReimageVmInstance(VolumeInventory inventory) { + String vmUuid = inventory.getVmInstanceUuid(); + if (vmUuid == null) { + return; + } + + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .delete(); + SQL.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) + .delete(); + + logger.debug(String.format("reset TPM state for VM[uuid:%s] after reimage: " + + "deleted all VmHostFileVO and VmHostBackupFileVO records", vmUuid)); + } + + @Override + public void releaseVmResource(VmInstanceSpec spec, Completion completion) { + if (spec.getDestHost() == null) { + completion.success(); + return; + } + + String hostUuid = spec.getDestHost().getUuid(); + String vmUuid = spec.getVmInventory().getUuid(); + List vmHostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .list(); + if (vmHostFiles.isEmpty()) { + completion.success(); + return; + } + + logger.info(String.format("try to sync VM host file[vmUuid=%s] from host[uuid=%s]", vmUuid, hostUuid)); + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(hostUuid); + syncMsg.setVmUuid(vmUuid); + syncMsg.setSyncReason(ResourceRelease.reason()); + + for (VmHostFileVO file : vmHostFiles) { + if (file.getType() == NvRam) { + syncMsg.setNvRamPath(file.getPath()); + } else if (file.getType() == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(file.getPath()); + } else { + logger.warn(String.format("unsupported vm host file type: %s, skip syncing for VM[uuid:%s] from host[uuid:%s]", + file.getType(), vmUuid, hostUuid)); + } + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + logger.warn(String.format("failed to sync VM host file[vmUuid=%s] from host[uuid=%s] but still continue: %s", + vmUuid, hostUuid, reply.getError().getReadableDetails())); + } + completion.success(); + } + }); + } + + @Override + public void beforeHaStartVmInstance(String vmUuid, String judgerClassName, List softAvoidHostUuids, Completion completion) { + Tuple hostUuidTuple = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findTuple(); + if (hostUuidTuple == null) { + completion.success(); + return; + } + + String hostUuid = hostUuidTuple.get(0, String.class); + final String finalHostUuid = (hostUuid == null) ? hostUuidTuple.get(1, String.class) : hostUuid; + if (finalHostUuid == null) { + logger.debug("skip HA file sync: VM has no host/lastHost record"); + completion.success(); + return; + } + + List vmHostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, finalHostUuid) + .list(); + if (vmHostFiles.isEmpty()) { + completion.success(); + return; + } + + logger.info(String.format("try to sync VM host file[vmUuid=%s] from last host[uuid=%s] before HA start", + vmUuid, finalHostUuid)); + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(finalHostUuid); + syncMsg.setVmUuid(vmUuid); + syncMsg.setSyncReason(BeforeHaStart.reason()); + + for (VmHostFileVO file : vmHostFiles) { + if (file.getType() == NvRam) { + syncMsg.setNvRamPath(file.getPath()); + } else if (file.getType() == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(file.getPath()); + } else { + logger.warn(String.format( + "unsupported vm host file type: %s, skip syncing for VM[uuid:%s] from host[uuid:%s]", + file.getType(), vmUuid, finalHostUuid)); + } + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + logger.warn(String.format("failed to sync VM host file[vmUuid=%s] from last host[uuid=%s] before HA start, but tolerated: %s", + vmUuid, finalHostUuid, reply.getError().getReadableDetails())); + } + completion.success(); + } + }); + } + + @Override + public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid, NoErrorCompletion completion) { + String destHostUuid = inv.getHostUuid(); + String vmUuid = inv.getUuid(); + + List vmHostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, srcHostUuid) + .list(); + if (vmHostFiles.isEmpty()) { + completion.done(); + return; + } + + // clean up stale VmHostFileVO/VmHostFileContentVO on dest host + // before sync creates new records + List staleFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, destHostUuid) + .list(); + if (!staleFiles.isEmpty()) { + List staleUuids = staleFiles.stream() + .map(VmHostFileVO::getUuid) + .collect(java.util.stream.Collectors.toList()); + SQL.New(VmHostFileContentVO.class) + .in(VmHostFileContentVO_.uuid, staleUuids) + .delete(); + SQL.New(VmHostFileVO.class) + .in(VmHostFileVO_.uuid, staleUuids) + .delete(); + logger.debug(String.format("cleaned up %d stale VmHostFileVO/Content on dest host[uuid=%s] for VM[uuid=%s]", + staleFiles.size(), destHostUuid, vmUuid)); + } + + logger.info(String.format("sync VM host file[vmUuid=%s] from dest host[uuid=%s] after migration", + vmUuid, destHostUuid)); + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(destHostUuid); + syncMsg.setVmUuid(vmUuid); + syncMsg.setSyncReason(PostMigration.reason()); + + for (VmHostFileVO file : vmHostFiles) { + if (file.getType() == NvRam) { + syncMsg.setNvRamPath(file.getPath()); + } else if (file.getType() == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(file.getPath()); + } else { + logger.warn(String.format("unsupported vm host file type: %s, skip syncing for VM[uuid:%s]", + file.getType(), vmUuid)); + } + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + logger.warn(String.format("failed to sync VM host file[vmUuid=%s] from host[uuid=%s] after migration, but tolerated: %s", + vmUuid, destHostUuid, reply.getError().getReadableDetails())); + } + completion.done(); + } + }); + } + + @Override + public void afterVolumeLiveSnapshotGroupCreatedOnBackend(CreateVolumesSnapshotOverlayInnerMsg msg, + TakeVolumesSnapshotOnKvmReply treply, + Completion completion) { + if (treply == null || !treply.isSuccess()) { + completion.success(); + return; + } + + if (!msg.isBackupHostFileIfNeeded()) { + completion.success(); + return; + } + + String vmUuid = msg.getLockedVmInstanceUuids().get(0); + String hostUuid = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + + List hostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .list(); + + if (hostFiles.isEmpty()) { + completion.success(); + return; + } + + String tempResourceUuid = Platform.getUuid(); + + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setVmUuid(vmUuid); + syncMsg.setHostUuid(hostUuid); + + for (VmHostFileVO file : hostFiles) { + if (file.getType() == NvRam) { + syncMsg.setNvRamPath(buildNvramSnapshotBackupFilePath(vmUuid)); + } else if (file.getType() == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(buildTpmStateSnapshotBackupFilePath(vmUuid)); + } + } + + syncMsg.setSyncReason(SnapshotGroupOnlineBackup.reason()); + syncMsg.setSyncToBackup(true); + syncMsg.setBackupResourceUuid(tempResourceUuid); + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + treply.setHostBackupTempResourceUuid(tempResourceUuid); + logger.debug(String.format("synced backup host files for vm[uuid:%s] to VmHostBackupFileVO[resourceUuid:%s]", + vmUuid, tempResourceUuid)); + } else { + logger.warn(String.format("failed to sync backup host files for vm[uuid:%s] during online snapshot, " + + "but tolerated: %s", vmUuid, reply.getError().getReadableDetails())); + } + completion.success(); + } + }); + } + + @Override + public void afterVolumeLiveSnapshotGroupCreationFailsOnBackend(CreateVolumesSnapshotOverlayInnerMsg msg, + TakeVolumesSnapshotOnKvmReply treply) { + // No cleanup needed — backup files on agent side are ephemeral + } + + @Override + public void afterVolumeSnapshotGroupCreated(VolumeSnapshotGroupInventory snapshotGroup, + ConsistentType consistentType, + Completion completion) { + completion.success(); + } + + @Override + public void afterVolumeSnapshotCreated(VolumeSnapshotInventory snapshot, Completion completion) { + completion.success(); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java new file mode 100644 index 00000000000..5a62cc6dc6d --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java @@ -0,0 +1,1067 @@ +package org.zstack.kvm.efi; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.Platform; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.cloudbus.EventCallback; +import org.zstack.core.cloudbus.EventFacadeImpl; +import org.zstack.core.cloudbus.MessageSafe; +import org.zstack.core.cloudbus.ResourceDestinationMaker; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.db.SQLBatch; +import org.zstack.core.timeout.TimeHelper; +import org.zstack.core.workflow.SimpleFlowChain; +import org.zstack.header.AbstractService; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.core.workflow.Flow; +import org.zstack.header.core.workflow.FlowDoneHandler; +import org.zstack.header.core.workflow.FlowErrorHandler; +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.exception.CloudRuntimeException; +import org.zstack.header.message.Message; +import org.zstack.header.message.MessageReply; +import org.zstack.header.vm.VmCanonicalEvents; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.RestoreVmHostFileMsg; +import org.zstack.header.vm.additions.RestoreVmHostFileReply; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileContentFormat; +import org.zstack.header.vm.additions.VmHostFileContentVO; +import org.zstack.header.vm.additions.VmHostFileContentVO_; +import org.zstack.header.vm.additions.VmHostFileOperation; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.kvm.KVMConstant; +import org.zstack.kvm.KvmCommandSender; +import org.zstack.kvm.KvmResponseWrapper; +import org.zstack.kvm.vmfiles.message.BackupVmHostFileMsg; +import org.zstack.kvm.vmfiles.message.BackupVmHostFileOnHypervisorMsg; +import org.zstack.kvm.vmfiles.message.BackupVmHostFileOnHypervisorReply; +import org.zstack.kvm.vmfiles.message.BackupVmHostFileReply; +import org.zstack.kvm.vmfiles.message.CloneVmHostFileMsg; +import org.zstack.kvm.vmfiles.message.CloneVmHostFileReply; +import org.zstack.kvm.vmfiles.message.SyncVmHostFilesFromHostMsg; +import org.zstack.kvm.vmfiles.message.SyncVmHostFilesFromHostReply; +import org.zstack.resourceconfig.ResourceConfig; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.DebugUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.Tuple; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import static org.zstack.compute.vm.VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE; +import static org.zstack.core.Platform.operr; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.PostClone; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.Restore; +import static org.zstack.header.vm.additions.VmHostFileSyncReason.VmShutdown; +import static org.zstack.kvm.KVMAgentCommands.*; +import static org.zstack.kvm.KVMConstant.*; +import static org.zstack.utils.CollectionDSL.list; +import static org.zstack.utils.CollectionUtils.findOneOrNull; +import static org.zstack.utils.CollectionUtils.toMap; +import static org.zstack.utils.CollectionUtils.transform; + +public class KvmSecureBootManager extends AbstractService { + private static final CLogger logger = Utils.getLogger(KvmSecureBootManager.class); + + @Autowired + private CloudBus bus; + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private EventFacadeImpl eventFacade; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private KvmVmHostFileFactory vmHostFileFactory; + @Autowired + private TimeHelper timeHelper; + @Autowired + private ResourceDestinationMaker resourceDestinationMaker; + + @Override + public boolean start() { + setupCanonicalEvents(); + return true; + } + + @Override + public boolean stop() { + return true; + } + + @SuppressWarnings("rawtypes") + private void setupCanonicalEvents() { + eventFacade.on(VmCanonicalEvents.VM_LIBVIRT_REPORT_START, new EventCallback() { + @Override + protected void run(Map tokens, Object data) { + String vmUuid = (String) data; + boolean managedByMe = resourceDestinationMaker.isManagedByUs(vmUuid); + if (!managedByMe) { + return; + } + markVmHostFilesChanged(vmUuid); + } + }); + + eventFacade.on(VmCanonicalEvents.VM_LIBVIRT_REPORT_SHUTDOWN, new EventCallback() { + @Override + protected void run(Map tokens, Object data) { + String vmUuid = (String) data; + boolean managedByMe = resourceDestinationMaker.isManagedByUs(vmUuid); + if (!managedByMe) { + return; + } + + Tuple tuple = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findTuple(); + if (tuple == null) { + return; + } + + String hostUuid = (String) tuple.get(0); + if (hostUuid == null) { + hostUuid = (String) tuple.get(1); + } + markVmHostFilesChanged(vmUuid, hostUuid); + + List hostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .in(VmHostFileVO_.type, list(VmHostFileType.NvRam, VmHostFileType.TpmState)) + .list(); + if (hostFiles.isEmpty()) { + return; + } + + VmHostFileVO nvRamFile = findOneOrNull(hostFiles, it -> it.getType() == VmHostFileType.NvRam); + VmHostFileVO tpmStateFile = findOneOrNull(hostFiles, it -> it.getType() == VmHostFileType.TpmState); + if (nvRamFile == null && tpmStateFile == null) { + return; + } + + SyncVmHostFilesFromHostMsg innerMessage = new SyncVmHostFilesFromHostMsg(); + innerMessage.setHostUuid(hostUuid); + innerMessage.setVmUuid(vmUuid); + innerMessage.setNvRamPath(nvRamFile == null ? null : nvRamFile.getPath()); + innerMessage.setTpmStateFolder(tpmStateFile == null ? null : tpmStateFile.getPath()); + innerMessage.setSyncReason(VmShutdown.reason()); + bus.makeLocalServiceId(innerMessage, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(innerMessage, new CloudBusCallBack(null) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + logger.info(String.format("success to read file content from host[uuid=%s]", + innerMessage.getHostUuid())); + } else { + logger.warn(String.format("failed to read file content from host[uuid=%s]: %s", + innerMessage.getHostUuid(), reply.getError().getReadableDetails())); + } + } + }); + } + }); + } + + /** + * Preemptive judgment: when a VM with TPM (or enabled secure boot) starts or shuts down, + * the NvRam/TpmState data must have changed, so mark the corresponding + * VmHostFileVO.changeDate to current time. + */ + private void markVmHostFilesChanged(String vmUuid) { + Tuple tuple = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findTuple(); + if (tuple == null) { + return; + } + + String hostUuid = (String) tuple.get(0); + if (hostUuid == null) { + hostUuid = (String) tuple.get(1); + } + if (hostUuid == null) { + return; + } + + markVmHostFilesChanged(vmUuid, hostUuid); + } + + private void markVmHostFilesChanged(String vmUuid, String hostUuid) { + if (hostUuid == null) { + return; + } + + final Set types = vmHostFileFactory.vmHostFileTypeNeedRegisterForVm(vmUuid); + if (types.isEmpty()) { + return; + } + + Timestamp now = new Timestamp(timeHelper.getCurrentTimeMillis()); + long updated = SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .in(VmHostFileVO_.type, types) + .set(VmHostFileVO_.changeDate, now) + .update(); + + if (updated > 0) { + logger.debug(String.format("preemptively marked VmHostFiles as changed for VM[uuid:%s] on host[uuid:%s], %d records updated", + vmUuid, hostUuid, updated)); + } + } + + @Override + public String getId() { + return bus.makeLocalServiceId(VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + } + + @Override + @MessageSafe + public void handleMessage(Message msg) { + if (msg instanceof SyncVmHostFilesFromHostMsg) { + handle((SyncVmHostFilesFromHostMsg) msg); + } else if (msg instanceof CloneVmHostFileMsg) { + handle((CloneVmHostFileMsg) msg); + } else if (msg instanceof BackupVmHostFileMsg) { + handle((BackupVmHostFileMsg) msg); + } else if (msg instanceof RestoreVmHostFileMsg) { + handle((RestoreVmHostFileMsg) msg); + } else if (msg instanceof BackupVmHostFileOnHypervisorMsg) { + handle((BackupVmHostFileOnHypervisorMsg) msg); + } else { + bus.dealWithUnknownMessage(msg); + } + } + + static class CloneVmHostFileContext { + List typesNeedClone = new ArrayList<>(); + List files = new ArrayList<>(); + List backupFiles = new ArrayList<>(); + List syncContexts = new ArrayList<>(); + } + + private void handle(SyncVmHostFilesFromHostMsg msg) { + KvmCommandSender sender = new KvmCommandSender(msg.getHostUuid()) + .disableHostStatusCheck(); + + KVMAgentCommands.ReadVmHostFileContentCmd cmd = new KVMAgentCommands.ReadVmHostFileContentCmd(); + cmd.setHostFiles(new ArrayList<>()); + if (msg.getTpmStateFolder() != null) { + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(msg.getTpmStateFolder()); + to.setType(VmHostFileType.TpmState.toString()); + cmd.getHostFiles().add(to); + } + if (msg.getNvRamPath() != null) { + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(msg.getNvRamPath()); + to.setType(VmHostFileType.NvRam.toString()); + cmd.getHostFiles().add(to); + } + long now = timeHelper.getCurrentTimeMillis(); + + SyncVmHostFilesFromHostReply reply = new SyncVmHostFilesFromHostReply(); + sender.send(cmd, READ_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.ReadVmHostFileContentResponse readRsp = wrapper.getResponse(KVMAgentCommands.ReadVmHostFileContentResponse.class); + return readRsp.isSuccess() ? null : + operr("failed to read file content response").withException(readRsp.getError()); + }, new ReturnValueCompletion(msg) { + @Override + public void success(KvmResponseWrapper wrapper) { + KVMAgentCommands.ReadVmHostFileContentResponse readRsp = wrapper.getResponse(KVMAgentCommands.ReadVmHostFileContentResponse.class); + if (!readRsp.isSuccess()) { + reply.setError(operr("failed to read file content response").withException(readRsp.getError())); + bus.reply(msg, reply); + return; + } + + ErrorCode error; + if (msg.isSyncToBackup()) { + error = syncToBackupFiles(msg, readRsp); + } else { + error = syncToHostFiles(msg, cmd, readRsp, now); + } + + if (error != null) { + reply.setError(error); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + private ErrorCode syncToHostFiles(SyncVmHostFilesFromHostMsg msg, + KVMAgentCommands.ReadVmHostFileContentCmd cmd, + KVMAgentCommands.ReadVmHostFileContentResponse readRsp, + long timeBeforeSync) { + final List existsFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, msg.getVmUuid()) + .eq(VmHostFileVO_.hostUuid, msg.getHostUuid()) + .in(VmHostFileVO_.path, cmd.getPaths()) + .list(); + final List existsContentUuid; + if (!existsFiles.isEmpty()) { + existsContentUuid = Q.New(VmHostFileContentVO.class) + .in(VmHostFileContentVO_.uuid, transform(existsFiles, VmHostFileVO::getUuid)) + .select(VmHostFileContentVO_.uuid) + .listValues(); + } else { + existsContentUuid = Collections.emptyList(); + } + + Timestamp syncTime = new Timestamp(timeBeforeSync); + List errors = new ArrayList<>(); + for (String path : cmd.getPaths()) { + KVMAgentCommands.VmHostFileTO to = findOneOrNull(readRsp.getHostFiles(), item -> item.getPath().equals(path)); + if (to == null) { + continue; + } + if (to.getError() != null) { + errors.add(operr("failed to read file %s", path) + .withOpaque("path", path) + .withException(to.getError())); + continue; + } + + VmHostFileType type = Objects.equals(path, msg.getNvRamPath()) ? + VmHostFileType.NvRam : VmHostFileType.TpmState; + + VmHostFileVO file = findOneOrNull(existsFiles, item -> item.getPath().equals(path)); + boolean fileExists = file != null; + + if (fileExists) { + String fileUuid = file.getUuid(); + new SQLBatch() { + @Override + protected void scripts() { + sql(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, fileUuid) + .set(VmHostFileVO_.lastSyncReason, msg.getSyncReason()) + .set(VmHostFileVO_.lastSyncDate, syncTime) + .set(VmHostFileVO_.lastOpDate, syncTime) + .update(); + sql(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, fileUuid) + .lt(VmHostFileVO_.changeDate, syncTime) + .set(VmHostFileVO_.changeDate, null) // CAS update + .update(); + } + }.execute(); + + } else { + file = new VmHostFileVO(); + file.setUuid(Platform.getUuid()); + file.setHostUuid(msg.getHostUuid()); + file.setVmInstanceUuid(msg.getVmUuid()); + file.setPath(path); + file.setType(type); + file.setLastSyncReason(msg.getSyncReason()); + file.setLastSyncDate(syncTime); + file.setChangeDate(null); + file.setLastOpDate(syncTime); + file.setCreateDate(syncTime); + file.setResourceName(String.format("%s file for %s", type, msg.getVmUuid())); + databaseFacade.persist(file); + } + + byte[] bytes = Base64.getDecoder().decode(to.getContentBase64()); + if (existsContentUuid.contains(file.getUuid())) { + SQL.New(VmHostFileContentVO.class) + .eq(VmHostFileContentVO_.uuid, file.getUuid()) + .set(VmHostFileContentVO_.content, bytes) + .set(VmHostFileContentVO_.format, VmHostFileContentFormat.valueOf(to.getFileFormat())) + .set(VmHostFileContentVO_.lastOpDate, syncTime) + .update(); + } else { + VmHostFileContentVO content = new VmHostFileContentVO(); + content.setUuid(file.getUuid()); + content.setContent(bytes); + content.setFormat(VmHostFileContentFormat.valueOf(to.getFileFormat())); + content.setCreateDate(syncTime); + content.setLastOpDate(syncTime); + databaseFacade.persist(content); + } + + if (logger.isTraceEnabled()) { + logger.trace(String.format("persist/update VmHostFileContentVO [uuid=%s]", file.getUuid())); + } + } + + if (errors.isEmpty()) { + return null; + } + + return operr("failed to read file content from host[uuid=%s]", msg.getHostUuid()) + .withCause(errors); + } + + private ErrorCode syncToBackupFiles(SyncVmHostFilesFromHostMsg msg, + KVMAgentCommands.ReadVmHostFileContentResponse readRsp) { + if (msg.getBackupResourceUuid() == null || msg.getBackupResourceUuid().isEmpty()) { + return operr("backupResourceUuid is required when syncToBackup is true"); + } + + // Query the source VmHostFileVO records for afterBackup callback + final List sourceHostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, msg.getVmUuid()) + .eq(VmHostFileVO_.hostUuid, msg.getHostUuid()) + .list(); + + List backupFilesToPersist = new ArrayList<>(); + List contentsToPersist = new ArrayList<>(); + Map backupFromMap = new HashMap<>(); + + List errors = new ArrayList<>(); + Timestamp now = Timestamp.from(Instant.now()); + + for (KVMAgentCommands.VmHostFileTO to : readRsp.getHostFiles()) { + if (to == null) { + continue; + } + if (to.getError() != null) { + errors.add(operr("failed to read backup file %s", to.getPath()) + .withOpaque("path", to.getPath()) + .withException(to.getError())); + continue; + } + if (to.getContentBase64() == null) { + errors.add(operr("backup file %s returns empty content", to.getPath()) + .withOpaque("path", to.getPath())); + continue; + } + + VmHostFileType type = VmHostFileType.valueOf(to.getType()); + String expectPath = KVMConstant.buildSnapshotBackupPathForVmHostFileType(type, msg.getVmUuid()); + if (!(Objects.equals(to.getPath(), expectPath))) { + errors.add(operr("unexpected path %s for backup file type %s", to.getPath(), to.getType()) + .withOpaque("path", to.getPath()) + .withOpaque("type", to.getType())); + continue; + } + + VmHostBackupFileVO backupFile = new VmHostBackupFileVO(); + backupFile.setUuid(Platform.getUuid()); + backupFile.setResourceUuid(msg.getBackupResourceUuid()); + backupFile.setType(type); + backupFile.setCreateDate(now); + backupFile.setLastOpDate(now); + backupFilesToPersist.add(backupFile); + + VmHostFileContentVO content = new VmHostFileContentVO(); + content.setUuid(backupFile.getUuid()); + content.setContent(Base64.getDecoder().decode(to.getContentBase64())); + content.setFormat(VmHostFileContentFormat.valueOf(to.getFileFormat())); + content.setCreateDate(now); + content.setLastOpDate(now); + contentsToPersist.add(content); + + VmHostFileVO sourceFile = findOneOrNull(sourceHostFiles, item -> item.getType() == type); + if (sourceFile != null) { + backupFromMap.put(backupFile, sourceFile); + } + } + + new SQLBatch() { + @Override + protected void scripts() { + for (VmHostBackupFileVO bf : backupFilesToPersist) { + sql(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, bf.getResourceUuid()) + .eq(VmHostBackupFileVO_.type, bf.getType()) + .delete(); + } + + if (!backupFilesToPersist.isEmpty()) { + databaseFacade.persistCollection(backupFilesToPersist); + } + if (!contentsToPersist.isEmpty()) { + databaseFacade.persistCollection(contentsToPersist); + } + } + }.execute(); + + for (VmHostBackupFileVO backup : backupFilesToPersist) { + VmHostFileVO source = backupFromMap.get(backup); + if (source != null) { + try { + vmHostFileFactory.createBackupBase(backup).afterBackup(source); + } catch (Exception e) { + logger.warn(String.format("failed to execute afterBackup hook for VmHostBackupFileVO[uuid:%s, type:%s]: %s", + backup.getUuid(), backup.getType(), e.getMessage()), e); + } + } + } + + if (errors.isEmpty()) { + return null; + } + + return operr("failed to read backup file content from host[uuid=%s]", msg.getHostUuid()) + .withCause(errors); + } + + @SuppressWarnings("rawtypes") + private void handle(CloneVmHostFileMsg msg) { + CloneVmHostFileReply reply = new CloneVmHostFileReply(); + + final Set types = vmHostFileFactory.vmHostFileTypeNeedRegisterForVm(msg.getSrcVmUuid()); + if (types.isEmpty()) { + bus.reply(msg, reply); + return; + } + + CloneVmHostFileContext context = new CloneVmHostFileContext(); + if (types.contains(VmHostFileType.NvRam)) { + context.typesNeedClone.add(VmHostFileType.NvRam); + } + + if (types.contains(VmHostFileType.TpmState)) { + boolean resetTpm; + if (msg.getResetTpm() == null) { + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity()); + resetTpm = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class); + } else { + resetTpm = msg.getResetTpm(); + } + if (!resetTpm) { + context.typesNeedClone.add(VmHostFileType.TpmState); + } + } + logger.debug(String.format("clone VM[uuid=%s] host files for types: %s", msg.getSrcVmUuid(), context.typesNeedClone)); + + SimpleFlowChain chain = new SimpleFlowChain(); + chain.setName("clone-vm-host-file"); + chain.then(new NoRollbackFlow() { + String __name__ = "prepare-sync-vm-host-file-context-list"; + + @Override + public void run(FlowTrigger trigger, Map data) { + for (VmHostFileType type : context.typesNeedClone) { + VmHostFileVO file = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, msg.getSrcVmUuid()) + .eq(VmHostFileVO_.type, type) + .orderByDesc(VmHostFileVO_.lastSyncDate) + .limit(1) + .find(); + if (file == null) { + logger.debug(String.format("skip to read/write %s host file for VM[vmUuid=%s]: file is not registered in MN", + type, msg.getSrcVmUuid())); + continue; + } + context.files.add(file); + } + + if (context.files.isEmpty()) { + trigger.next(); + return; + } + + Map contextMap = new HashMap<>(); + for (VmHostFileVO file : context.files) { + contextMap.computeIfAbsent(file.getHostUuid(), hostUuid -> { + SyncVmHostFilesFromHostMsg syncContext = new SyncVmHostFilesFromHostMsg(); + syncContext.setHostUuid(hostUuid); + syncContext.setVmUuid(msg.getSrcVmUuid()); + syncContext.setSyncReason(PostClone.reason()); + return syncContext; + }); + } + context.syncContexts.addAll(contextMap.values()); + + for (VmHostFileVO file : context.files) { + SyncVmHostFilesFromHostMsg syncContext = contextMap.get(file.getHostUuid()); + if (file.getType() == VmHostFileType.NvRam) { + syncContext.setNvRamPath(file.getPath()); + } else if (file.getType() == VmHostFileType.TpmState) { + syncContext.setTpmStateFolder(file.getPath()); + } else { + throw new CloudRuntimeException("unsupported vm host file type: " + file.getType()); + } + } + + trigger.next(); + } + }).then(new NoRollbackFlow() { + String __name__ = "read-vm-host-file-from-origin-host"; + + @Override + public boolean skip(Map data) { + return context.syncContexts.isEmpty(); + } + + @Override + public void run(FlowTrigger trigger, Map data) { + new While<>(context.syncContexts).each((syncContext, whileContext) -> { + bus.makeLocalServiceId(syncContext, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncContext, new CloudBusCallBack(whileContext) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + whileContext.addError(reply.getError()); + } + whileContext.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.isEmpty()) { + logger.warn(String.format("failed to sync host file for VM[uuid=%s] but still continue:\n%s", + msg.getSrcVmUuid(), + String.join("\n", transform(errorCodeList.getCauses(), ErrorCode::getReadableDetails)))); + } + trigger.next(); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "determine-content-uuid"; + + @Override + public void run(FlowTrigger trigger, Map data) { + List missingTypes = new ArrayList<>(context.typesNeedClone); + missingTypes.removeAll(transform(context.files, VmHostFileVO::getType)); + if (missingTypes.isEmpty()) { + trigger.next(); + return; + } + + context.backupFiles.addAll(Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, msg.getSrcVmUuid()) + .in(VmHostFileVO_.type, missingTypes) + .list()); + trigger.next(); + } + }).then(new NoRollbackFlow() { + String __name__ = "copy-host-content-database"; + + @Override + public boolean skip(Map data) { + return context.files.isEmpty() && context.backupFiles.isEmpty(); + } + + @Override + public void run(FlowTrigger trigger, Map data) { + List uuidList = transform(context.files, VmHostFileVO::getUuid); + List filesAfterSyncing = Q.New(VmHostFileVO.class) + .in(VmHostFileVO_.uuid, uuidList) + .list(); + backupVmHostFile(filesAfterSyncing, context.backupFiles, msg.getDstVmUuidList()); + trigger.next(); + } + }).done(new FlowDoneHandler(msg) { + @Override + public void handle(Map data) { + bus.reply(msg, reply); + } + }).error(new FlowErrorHandler(msg) { + @Override + public void handle(ErrorCode errCode, Map data) { + reply.setError(errCode); + bus.reply(msg, reply); + } + }).start(); + } + + private void handle(BackupVmHostFileMsg msg) { + BackupVmHostFileReply reply = new BackupVmHostFileReply(); + List filesNeedPersists = backupVmHostFile( + msg.getVmUuid(), msg.getHostUuid(), msg.getToResourceUuidList()); + reply.setBackupFileUuidList(transform(filesNeedPersists, VmHostBackupFileVO::getUuid)); + bus.reply(msg, reply); + } + + private List backupVmHostFile(String fromVmUuid, String hostUuid, List toResourceList) { + List hostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, fromVmUuid) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .list(); + + if (hostFiles.isEmpty()) { + return new ArrayList<>(); + } + return backupVmHostFile(hostFiles, new ArrayList<>(), toResourceList); + } + + private List backupVmHostFile(List fileList, List backupFiles, List toResourceList) { + List uuidList = transform(fileList, VmHostFileVO::getUuid); + uuidList.addAll(transform(backupFiles, VmHostBackupFileVO::getUuid)); + List contents = Q.New(VmHostFileContentVO.class) + .in(VmHostFileContentVO_.uuid, uuidList) + .list(); + + List filesNeedPersists = new ArrayList<>(); + List contentsNeedPersists = new ArrayList<>(); + // value is VmHostBackupFileVO or VmHostFileVO + Map backupFromMap = new HashMap<>(); + + Timestamp now = Timestamp.from(Instant.now()); + for (String resourceUuid : toResourceList) { + for (String uuid : uuidList) { + VmHostFileContentVO srcContent = findOneOrNull(contents, + item -> item.getUuid().equals(uuid)); + if (srcContent == null) { + continue; + } + + VmHostFileVO vmHostFile = findOneOrNull(fileList, + item -> item.getUuid().equals(uuid)); + VmHostBackupFileVO vmHostBackupFile = vmHostFile == null ? + findOneOrNull(backupFiles, item -> item.getUuid().equals(uuid)) : null; + DebugUtils.Assert(vmHostFile != null || vmHostBackupFile != null, + "vmHostFile or vmHostBackupFile cannot be null"); + + VmHostBackupFileVO file = new VmHostBackupFileVO(); + file.setUuid(Platform.getUuid()); + file.setResourceUuid(resourceUuid); + file.setType(vmHostFile == null ? vmHostBackupFile.getType() : vmHostFile.getType()); + file.setCreateDate(now); + file.setLastOpDate(now); + filesNeedPersists.add(file); + backupFromMap.put(file, vmHostFile == null ? vmHostBackupFile : vmHostFile); + + VmHostFileContentVO content = new VmHostFileContentVO(); + content.setUuid(file.getUuid()); + content.setContent(srcContent.getContent()); + content.setFormat(srcContent.getFormat()); + content.setCreateDate(now); + content.setLastOpDate(now); + contentsNeedPersists.add(content); + } + } + + if (logger.isTraceEnabled()) { + logger.trace(String.format("persist VmHostFileContentVO [uuid=%s]", + transform(contentsNeedPersists, VmHostFileContentVO::getUuid))); + } + + new SQLBatch() { + @Override + protected void scripts() { + for (VmHostBackupFileVO backupFile : filesNeedPersists) { + // resourceUuid + type must be unique in DB + sql(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, backupFile.getResourceUuid()) + .eq(VmHostBackupFileVO_.type, backupFile.getType()) + .delete(); + } + + if (!filesNeedPersists.isEmpty()) { + databaseFacade.persistCollection(filesNeedPersists); + } + if (!contentsNeedPersists.isEmpty()) { + databaseFacade.persistCollection(contentsNeedPersists); + } + } + }.execute(); + + for (VmHostBackupFileVO backup : filesNeedPersists) { + final Object backupFrom = backupFromMap.get(backup); + try { + if (backupFrom instanceof VmHostBackupFileVO) { + vmHostFileFactory.createBackupBase(backup).afterBackup((VmHostBackupFileVO) backupFrom); + } else if (backupFrom instanceof VmHostFileVO) { + vmHostFileFactory.createBackupBase(backup).afterBackup((VmHostFileVO) backupFrom); + } + } catch (Exception e) { + logger.warn(String.format("failed to execute afterBackup hook for VmHostBackupFileVO[uuid:%s, type:%s]: %s", + backup.getUuid(), backup.getType(), e.getMessage()), e); + } + } + + return filesNeedPersists; + } + + private void handle(RestoreVmHostFileMsg msg) { + RestoreVmHostFileReply reply = new RestoreVmHostFileReply(); + + List backupFiles = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, msg.getSnapshotGroupUuid()) + .list(); + + Tuple tuple = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid, VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, msg.getVmInstanceUuid()) + .findTuple(); + if (tuple == null) { + reply.setError(operr("VM instance [uuid:%s] not found", msg.getVmInstanceUuid())); + bus.reply(msg, reply); + return; + } + + String hostUuid = tuple.get(0, String.class); + if (hostUuid == null) { + hostUuid = tuple.get(1, String.class); + } + if (hostUuid == null) { + reply.setError(operr("VM instance [uuid:%s] has no host", msg.getVmInstanceUuid())); + bus.reply(msg, reply); + return; + } + + List currentHostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, msg.getVmInstanceUuid()) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .list(); + + Map currentFilesByType = new HashMap<>(); + for (VmHostFileVO file : currentHostFiles) { + currentFilesByType.put(file.getType(), file); + } + + Map backupFilesByType = new HashMap<>(); + for (VmHostBackupFileVO file : backupFiles) { + backupFilesByType.put(file.getType(), file); + } + + Set allTypes = new HashSet<>(); + allTypes.addAll(currentFilesByType.keySet()); + allTypes.addAll(backupFilesByType.keySet()); + + if (allTypes.isEmpty()) { + bus.reply(msg, reply); + return; + } + + // Batch query all backup file content before loop + List backupUuids = transform(backupFiles, VmHostBackupFileVO::getUuid); + Map backupContentMap = new HashMap<>(); + if (!backupUuids.isEmpty()) { + List backupContents = Q.New(VmHostFileContentVO.class) + .in(VmHostFileContentVO_.uuid, backupUuids) + .list(); + backupContentMap.putAll(toMap(backupContents, VmHostFileContentVO::getUuid, Function.identity())); + } + + List fileList = new ArrayList<>(); + for (VmHostFileType type : allTypes) { + VmHostFileTO to = new VmHostFileTO(); + to.setType(type.toString()); + + boolean hasCurrentFile = currentFilesByType.containsKey(type); + boolean hasBackupFile = backupFilesByType.containsKey(type); + + if (hasBackupFile) { + // Write operation + VmHostBackupFileVO backupFile = backupFilesByType.get(type); + VmHostFileContentVO content = backupContentMap.get(backupFile.getUuid()); + if (content == null) { + logger.warn(String.format("backup file content [uuid:%s] not found for type %s", + backupFile.getUuid(), type)); + continue; + } + + to.setPath(buildPathForVmHostFileType(type, msg.getVmInstanceUuid())); + to.setFileFormat(content.getFormat().toString()); + to.setOperation(VmHostFileOperation.Write.toString()); + String contentBase64 = Base64.getEncoder().encodeToString(content.getContent()); + to.setContentBase64(contentBase64); + + fileList.add(to); + } else if (hasCurrentFile) { + // Delete operation + VmHostFileVO currentFile = currentFilesByType.get(type); + to.setPath(currentFile.getPath()); + to.setOperation(VmHostFileOperation.Delete.toString()); + + fileList.add(to); + } + } + + if (fileList.isEmpty()) { + bus.reply(msg, reply); + return; + } + + final String finalHostUuid = hostUuid; + SimpleFlowChain.of("restore-vm-host-file") + .then(Flow.of("send-cmd") + .handle(trigger -> { + KVMAgentCommands.WriteVmHostFileContentCmd cmd = new KVMAgentCommands.WriteVmHostFileContentCmd(); + cmd.setHostFiles(fileList); + + KvmCommandSender sender = new KvmCommandSender(finalHostUuid); + sender.send(cmd, WRITE_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.WriteVmHostFileContentResponse writeRsp = wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); + return writeRsp.isSuccess() ? null : + operr("failed to write/delete host file response").withException(writeRsp.getError()); + }, new ReturnValueCompletion(trigger) { + @Override + public void success(KvmResponseWrapper wrapper) { + logger.info(String.format("success to restore host files for VM[uuid:%s] from snapshot group[uuid:%s]", + msg.getVmInstanceUuid(), msg.getSnapshotGroupUuid())); + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(operr("failed to restore host files for VM[uuid:%s]", msg.getVmInstanceUuid()) + .withCause(errorCode)); + } + }); + }) + .build()) + .then(Flow.of("persist-content-in-db") + .handle(trigger -> { + Timestamp now = Timestamp.from(Instant.now()); + + List allUuids = new ArrayList<>(); + allUuids.addAll(transform(backupFilesByType.values(), VmHostBackupFileVO::getUuid)); + allUuids.addAll(transform(currentFilesByType.values(), VmHostFileVO::getUuid)); + + Map contentMap = new HashMap<>(); + if (!allUuids.isEmpty()) { + List contents = Q.New(VmHostFileContentVO.class) + .in(VmHostFileContentVO_.uuid, allUuids) + .list(); + contentMap.putAll(toMap(contents, VmHostFileContentVO::getUuid, Function.identity())); + } + + for (VmHostFileType type : allTypes) { + boolean hasCurrentFile = currentFilesByType.containsKey(type); + boolean hasBackupFile = backupFilesByType.containsKey(type); + + if (hasBackupFile) { + VmHostBackupFileVO backupFile = backupFilesByType.get(type); + VmHostFileContentVO backupContent = contentMap.get(backupFile.getUuid()); + if (backupContent == null) { + continue; + } + + if (hasCurrentFile) { + // update existing VmHostFileVO and VmHostFileContentVO + VmHostFileVO currentFile = currentFilesByType.get(type); + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, currentFile.getUuid()) + .set(VmHostFileVO_.lastSyncReason, Restore.reason(msg.getSyncReason())) + .set(VmHostFileVO_.lastOpDate, now) + .set(VmHostFileVO_.lastSyncDate, now) + .update(); + + VmHostFileContentVO existingContent = contentMap.get(currentFile.getUuid()); + if (existingContent != null) { + SQL.New(VmHostFileContentVO.class) + .eq(VmHostFileContentVO_.uuid, currentFile.getUuid()) + .set(VmHostFileContentVO_.content, backupContent.getContent()) + .set(VmHostFileContentVO_.format, backupContent.getFormat()) + .set(VmHostFileContentVO_.lastOpDate, now) + .update(); + } else { + VmHostFileContentVO newContent = new VmHostFileContentVO(); + newContent.setUuid(currentFile.getUuid()); + newContent.setContent(backupContent.getContent()); + newContent.setFormat(backupContent.getFormat()); + newContent.setCreateDate(now); + newContent.setLastOpDate(now); + databaseFacade.persist(newContent); + } + } else { + // create new VmHostFileVO and VmHostFileContentVO + VmHostFileVO newFile = new VmHostFileVO(); + newFile.setUuid(Platform.getUuid()); + newFile.setResourceName(String.format("%s file for %s", type, msg.getVmInstanceUuid())); + newFile.setVmInstanceUuid(msg.getVmInstanceUuid()); + newFile.setHostUuid(finalHostUuid); + newFile.setType(type); + newFile.setPath(buildPathForVmHostFileType(type, msg.getVmInstanceUuid())); + newFile.setLastSyncReason(Restore.reason(msg.getSyncReason())); + newFile.setLastSyncDate(now); + newFile.setCreateDate(now); + newFile.setLastOpDate(now); + databaseFacade.persist(newFile); + + VmHostFileContentVO newContent = new VmHostFileContentVO(); + newContent.setUuid(newFile.getUuid()); + newContent.setContent(backupContent.getContent()); + newContent.setFormat(backupContent.getFormat()); + newContent.setCreateDate(now); + newContent.setLastOpDate(now); + databaseFacade.persist(newContent); + } + } else if (hasCurrentFile) { + // delete VmHostFileVO and VmHostFileContentVO + VmHostFileVO currentFile = currentFilesByType.get(type); + SQL.New(VmHostFileContentVO.class) + .eq(VmHostFileContentVO_.uuid, currentFile.getUuid()) + .delete(); + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, currentFile.getUuid()) + .delete(); + } + } + + trigger.next(); + }) + .build()) + .propagateExceptionTo(msg) + .done(() -> bus.reply(msg, reply)) + .error(errorCode -> { + reply.setError(errorCode); + bus.reply(msg, reply); + }) + .start(); + } + + private void handle(BackupVmHostFileOnHypervisorMsg msg) { + KvmCommandSender sender = new KvmCommandSender(msg.getHostUuid()); + + KVMAgentCommands.BackupVmHostFileCmd cmd = new KVMAgentCommands.BackupVmHostFileCmd(); + cmd.setVmHostFileBackupJobs(msg.getVmHostFileBackupJobs()); + + BackupVmHostFileOnHypervisorReply reply = new BackupVmHostFileOnHypervisorReply(); + sender.send(cmd, BACKUP_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.BackupVmHostFileResponse rsp = wrapper.getResponse(KVMAgentCommands.BackupVmHostFileResponse.class); + return rsp.isSuccess() ? null : + operr("failed to backup vm host file on hypervisor[hostUuid=%s]", msg.getHostUuid()) + .withOpaque("host.uuid", msg.getHostUuid()) + .withException(rsp.getError()); + }, new ReturnValueCompletion(msg) { + @Override + public void success(KvmResponseWrapper wrapper) { + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java new file mode 100644 index 00000000000..c4e13957cdf --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmVmHostFileFactory.java @@ -0,0 +1,47 @@ +package org.zstack.kvm.efi; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.kvm.tpm.TpmStateVmHostBackupFileBase; +import org.zstack.kvm.tpm.TpmStateVmHostFileBase; +import org.zstack.kvm.vmfiles.AbstractVmHostBackupFileBase; +import org.zstack.kvm.vmfiles.AbstractVmHostFileBase; +import org.zstack.resourceconfig.ResourceConfigFacade; + +import java.util.Set; + +import static org.zstack.core.Platform.operr; + +public class KvmVmHostFileFactory { + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private VmTpmManager vmTpmManager; + + public AbstractVmHostFileBase createBase(VmHostFileVO file) { + switch (file.getType()) { + case NvRam: return new NvRamVmHostFileBase(file); + case TpmState: return new TpmStateVmHostFileBase(file); + default: throw operr("invalid VM host file type: " + file.getType()).toException(); + } + } + + public AbstractVmHostBackupFileBase createBackupBase(VmHostBackupFileVO backupFile) { + switch (backupFile.getType()) { + case NvRam: return new NvRamVmHostBackupFileBase(backupFile); + case TpmState: return new TpmStateVmHostBackupFileBase(backupFile); + default: throw operr("invalid VM host file type: " + backupFile.getType()).toException(); + } + } + + public Set vmHostFileTypeNeedRegisterForVm(String vmUuid) { + return vmTpmManager.vmHostFileTypeNeedRegisterForVm(vmUuid); + } + + public boolean needRegister(VmHostFileType type, String vmUuid) { + return vmTpmManager.needRegister(type, vmUuid); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostBackupFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostBackupFileBase.java new file mode 100644 index 00000000000..8d78b9ac0d6 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostBackupFileBase.java @@ -0,0 +1,16 @@ +package org.zstack.kvm.efi; + +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.kvm.vmfiles.AbstractVmHostBackupFileBase; + +public class NvRamVmHostBackupFileBase extends AbstractVmHostBackupFileBase { + public NvRamVmHostBackupFileBase(VmHostBackupFileVO self) { + super(self); + } + + @Override + public VmHostFileType type() { + return VmHostFileType.NvRam; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostFileBase.java new file mode 100644 index 00000000000..cf539ee2278 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/efi/NvRamVmHostFileBase.java @@ -0,0 +1,16 @@ +package org.zstack.kvm.efi; + +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.kvm.vmfiles.AbstractVmHostFileBase; + +public class NvRamVmHostFileBase extends AbstractVmHostFileBase { + public NvRamVmHostFileBase(VmHostFileVO self) { + super(self); + } + + @Override + public VmHostFileType type() { + return VmHostFileType.NvRam; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java new file mode 100644 index 00000000000..c6302660f1d --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmExtensions.java @@ -0,0 +1,797 @@ +package org.zstack.kvm.tpm; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend.CloneEncryptedResourceKeyContext; +import org.zstack.core.Platform; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.workflow.SimpleFlowChain; +import org.zstack.header.core.Completion; +import org.zstack.header.errorcode.OperationFailureException; +import org.zstack.header.core.workflow.FlowDoneHandler; +import org.zstack.header.core.workflow.FlowErrorHandler; +import org.zstack.header.core.workflow.Flow; +import org.zstack.header.core.workflow.FlowRollback; +import org.zstack.header.core.workflow.FlowTrigger; +import org.zstack.header.core.workflow.NoRollbackFlow; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.core.NoErrorCompletion; +import org.zstack.header.host.HostConstant; +import org.zstack.header.message.MessageReply; +import org.zstack.header.keyprovider.EncryptedResourceKeyManager; +import org.zstack.header.keyprovider.EncryptedResourceKeyManager.GetOrCreateResourceKeyContext; +import org.zstack.header.keyprovider.EncryptedResourceKeyManager.ResourceKeyResult; +import org.zstack.header.secret.SecretHostDeleteMsg; +import org.zstack.header.secret.SecretHostDefineMsg; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.secret.SecretHostGetMsg; +import org.zstack.header.secret.SecretHostGetReply; +import org.zstack.header.tpm.entity.TpmSpec; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.HaStartVmInstanceMsg; +import org.zstack.header.vm.VmAfterExpungeExtensionPoint; +import org.zstack.header.vm.VmInstanceMigrateExtensionPoint; +import org.zstack.header.secret.SecretHostDefineReply; +import org.zstack.header.tpm.message.RemoveTpmMsg; +import org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceInventory; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstantiateResourceException; +import org.zstack.header.vm.VmStateChangedExtensionPoint; +import org.zstack.header.vm.VmJustBeforeDeleteFromDbExtensionPoint; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.header.vm.devices.VmDevicesSpec; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.kvm.KVMHostInventory; +import org.zstack.kvm.KVMStartVmExtensionPoint; +import org.zstack.kvm.efi.KvmSecureBootExtensions; +import org.zstack.kvm.efi.KvmSecureBootExtensions.*; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.sql.Timestamp; +import java.time.Instant; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +import static org.zstack.header.tpm.TpmConstants.SERVICE_ID; +import static org.zstack.kvm.KVMConstant.*; +import static org.zstack.core.Platform.operr; + +public class KvmTpmExtensions implements KVMStartVmExtensionPoint, + PreVmInstantiateResourceExtensionPoint, + VmInstanceMigrateExtensionPoint, + VmAfterExpungeExtensionPoint, + VmStateChangedExtensionPoint, + VmJustBeforeDeleteFromDbExtensionPoint { + private static final CLogger logger = Utils.getLogger(KvmTpmExtensions.class); + + @Autowired + private KvmSecureBootExtensions secureBootExtensions; + @Autowired + private DatabaseFacade databaseFacade; + @Autowired + private TpmEncryptedResourceKeyBackend resourceKeyBackend; + @Autowired + private EncryptedResourceKeyManager resourceKeyManager; + @Autowired + private CloudBus bus; + + private final Object hostFileLock = new Object(); + private final Map volumeMigratingSourceHostCache = new ConcurrentHashMap<>(); + + @Override + public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, KVMAgentCommands.StartVmCmd cmd) { + final VmDevicesSpec devicesSpec = spec.getDevicesSpec(); + if (devicesSpec == null || devicesSpec.getTpm() == null || !devicesSpec.getTpm().isEnable()) { + return; + } + + String keyProviderUuid = devicesSpec.getTpm().getKeyProviderUuid(); + if (StringUtils.isBlank(keyProviderUuid)) { + keyProviderUuid = resourceKeyBackend.findKeyProviderUuidByTpm(devicesSpec.getTpm().getTpmUuid()); + } + + TpmTO tpm = new TpmTO(); + tpm.setKeyProviderUuid(keyProviderUuid); + tpm.setSecretUuid(devicesSpec.getTpm().getSecretUuid()); + tpm.setInstallPath(buildTpmStateFilePath(cmd.getVmInstanceUuid())); + cmd.setTpm(tpm); + + synchronized (hostFileLock) { + VmHostFileVO tpmStateFile = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, cmd.getVmInstanceUuid()) + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) + .eq(VmHostFileVO_.hostUuid, host.getUuid()) + .find(); + if (tpmStateFile == null) { + tpmStateFile = new VmHostFileVO(); + tpmStateFile.setUuid(Platform.getUuid()); + tpmStateFile.setHostUuid(host.getUuid()); + tpmStateFile.setVmInstanceUuid(cmd.getVmInstanceUuid()); + tpmStateFile.setType(VmHostFileType.TpmState); + tpmStateFile.setPath(tpm.getInstallPath()); + tpmStateFile.setCreateDate(Timestamp.from(Instant.now())); + tpmStateFile.setResourceName("TpmState file for " + cmd.getVmInstanceUuid()); + databaseFacade.persist(tpmStateFile); + } else { + SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, tpmStateFile.getUuid()) + .set(VmHostFileVO_.path, tpm.getInstallPath()) + .set(VmHostFileVO_.lastOpDate, Timestamp.from(Instant.now())) + .update(); + } + } + } + + @Override + public void startVmOnKvmSuccess(KVMHostInventory host, VmInstanceSpec spec) { + if (spec.getMessage() instanceof HaStartVmInstanceMsg) { + String vmUuid = spec.getVmInventory() == null ? null : spec.getVmInventory().getUuid(); + String srcHostUuid = spec.getVmInventory() == null ? null : spec.getVmInventory().getLastHostUuid(); + Integer keyVersion = findTpmKeyVersionByVmUuid(vmUuid); + boolean vmIsOnDestHost = isVmCurrentlyOnExpectedHost(vmUuid, host.getUuid()); + if (vmIsOnDestHost && StringUtils.isNotBlank(srcHostUuid) && !host.getUuid().equals(srcHostUuid)) { + deleteHostSecretBestEffort(srcHostUuid, vmUuid, keyVersion, + "ha-start-success"); + } + } + clearRollbackInfo(spec); + } + + @Override + public void startVmOnKvmFailed(KVMHostInventory host, VmInstanceSpec spec, ErrorCode err) { + clearRollbackInfo(spec); + } + + @Override + public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException { + // do-nothing + } + + @Override + @SuppressWarnings("rawtypes") + public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) { + final VmDevicesSpec devicesSpec = spec.getDevicesSpec(); + if (devicesSpec == null || devicesSpec.getTpm() == null || !devicesSpec.getTpm().isEnable()) { + completion.success(); + return; + } + + TpmSpec tpmSpec = devicesSpec.getTpm(); + clearRollbackInfo(spec); + final PrepareTpmResourceContext context = new PrepareTpmResourceContext(); + context.tpmUuid = tpmSpec.getTpmUuid(); + context.backupFileUuid = tpmSpec.getBackupFileUuid(); // maybe null + context.providerUuid = resourceKeyBackend.findKeyProviderUuidByTpm(context.tpmUuid); + context.keyVersion = resourceKeyBackend.findKeyVersionByTpm(context.tpmUuid); + context.instantiateForNewVm = spec.getCurrentVmOperation() == VmInstanceConstant.VmOperation.NewCreate; + context.enableKeyProvider = !VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class); + + final SimpleFlowChain chain = new SimpleFlowChain(); + chain.setName("prepare-tpm-resources-for-vm-" + spec.getVmInventory().getUuid()); + chain.then(new NoRollbackFlow() { + String __name__ = "prepare-tpm-state-file-on-host"; + + @Override + public void run(FlowTrigger trigger, Map data) { + PrepareHostFileContext innerContext = new PrepareHostFileContext(); + innerContext.hostUuid = spec.getDestHost().getUuid(); + innerContext.vmUuid = spec.getVmInventory().getUuid(); + innerContext.type = VmHostFileType.TpmState; + innerContext.backupUuid = context.backupFileUuid; + innerContext.syncReason = "pre-instantiate VM resource"; + secureBootExtensions.prepareHostFileOnHost(innerContext, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }).then(new Flow() { + String __name__ = "clone-tpm-resource-key-from-snapshot-source"; + + @Override + public boolean skip(Map data) { + return !context.enableKeyProvider; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + String srcTpmUuid = findSourceTpmUuidFromSnapshotTpmBackupFile(context.backupFileUuid); + if (StringUtils.isBlank(srcTpmUuid)) { + trigger.next(); + return; + } + CloneEncryptedResourceKeyContext cloneCtx = new CloneEncryptedResourceKeyContext(); + cloneCtx.srcTpmUuid = srcTpmUuid; + cloneCtx.dstTpmUuid = context.tpmUuid; + cloneCtx.resetTpm = false; + resourceKeyBackend.cloneEncryptedResourceKey(cloneCtx, new Completion(trigger) { + @Override + public void success() { + context.providerUuid = resourceKeyBackend.findKeyProviderUuidByTpm(context.tpmUuid); + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + + // Use clone op above and will not set rollback flag TpmSpec.resourceKeyCreatedNew + // to true, so use flow rollback instead preReleaseVmResource rollback. And we + // definitely don't need to delete keytool secret on shapshot case. + @Override + public void rollback(FlowRollback trigger, Map data) { + if (StringUtils.isNotBlank(context.backupFileUuid)) { + try { + resourceKeyBackend.detachKeyProviderFromTpm(context.tpmUuid); + } catch (Exception e) { + logger.warn(String.format("failed to detach key provider ref for tpm[uuid:%s]: %s", + context.tpmUuid, e.getMessage())); + } + } + trigger.rollback(); + } + }).then(new NoRollbackFlow() { + String __name__ = "get-secret-on-host-first"; + + @Override + public boolean skip(Map data) { + return !context.enableKeyProvider; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + if (context.instantiateForNewVm && context.keyVersion == null) { + trigger.next(); + return; + } + if (StringUtils.isBlank(context.providerUuid)) { + trigger.fail(operr("missing TPM resource key binding for tpm[uuid:%s], attachKeyProviderToTpm must run before get-secret-on-host", + context.tpmUuid)); + return; + } + if (!context.instantiateForNewVm && context.keyVersion == null) { + trigger.fail(operr("missing keyVersion for tpm[uuid:%s] before get secret on host", context.tpmUuid)); + return; + } + // NewCreate cloned from an existing TPM may already carry keyVersion; allow it. + // For non-NewCreate, keyVersion must exist (validated above). + SecretHostGetMsg innerMsg = new SecretHostGetMsg(); + innerMsg.setHostUuid(spec.getDestHost().getUuid()); + innerMsg.setVmUuid(spec.getVmInventory().getUuid()); + innerMsg.setPurpose("vtpm"); + innerMsg.setKeyVersion(context.keyVersion); + innerMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(innerMsg, HostConstant.SERVICE_ID, innerMsg.getHostUuid()); + bus.send(innerMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + SecretHostGetReply r = reply.castReply(); + spec.getDevicesSpec().getTpm().setSecretUuid(r.getSecretUuid()); + trigger.next(); + return; + } + + ErrorCode errorCode = reply.getError(); + if (errorCode != null && isVtpmSecretNotFoundOnHost(errorCode)) { + trigger.next(); + return; + } + + trigger.fail(errorCode != null ? errorCode : operr("get secret on host failed")); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "get-or-create-key-and-dek"; + + @Override + public boolean skip(Map data) { + return !context.enableKeyProvider; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + GetOrCreateResourceKeyContext keyCtx = new GetOrCreateResourceKeyContext(); + keyCtx.setResourceUuid(context.tpmUuid); + keyCtx.setResourceType(TpmVO.class.getSimpleName()); + keyCtx.setKeyProviderUuid(context.providerUuid); + keyCtx.setPurpose("vtpm"); + + resourceKeyManager.getOrCreateKey(keyCtx, new ReturnValueCompletion(trigger) { + @Override + public void success(ResourceKeyResult result) { + tpmSpec.setResourceKeyCreatedNew(result.isCreatedNewKey()); + tpmSpec.setResourceKeyProviderUuid(result.getKeyProviderUuid()); + context.dekBase64 = result.getDekBase64(); + context.keyVersion = result.getKeyVersion(); + if (context.keyVersion == null) { + trigger.fail(operr("missing keyVersion for tpm[uuid:%s] after getOrCreateKey", context.tpmUuid)); + return; + } + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "define-secret-on-host"; + + @Override + public boolean skip(Map data) { + return !context.enableKeyProvider; + } + + @Override + public void run(FlowTrigger trigger, Map data) { + if (context.dekBase64 == null) { + trigger.fail(operr("missing dekBase64 for tpm[uuid:%s] before define-secret-on-host", context.tpmUuid)); + return; + } + + SecretHostDefineMsg innerMsg = new SecretHostDefineMsg(); + innerMsg.setHostUuid(spec.getDestHost().getUuid()); + innerMsg.setVmUuid(spec.getVmInventory().getUuid()); + innerMsg.setDekBase64(context.dekBase64); + innerMsg.setPurpose("vtpm"); + innerMsg.setKeyVersion(context.keyVersion); + innerMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + innerMsg.setDescription("Define secret for VM " + spec.getVmInventory().getUuid()); + bus.makeTargetServiceIdByResourceUuid(innerMsg, HostConstant.SERVICE_ID, innerMsg.getHostUuid()); + bus.send(innerMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + SecretHostDefineReply r = reply.castReply(); + spec.getDevicesSpec().getTpm().setSecretUuid(r.getSecretUuid()); + context.clearSensitiveData(); + trigger.next(); + } else { + context.clearSensitiveData(); + trigger.fail(reply.getError()); + } + } + }); + } + }).done(new FlowDoneHandler(completion) { + @Override + public void handle(Map data) { + context.clearSensitiveData(); + completion.success(); + } + }).error(new FlowErrorHandler(completion) { + @Override + public void handle(ErrorCode errCode, Map data) { + context.clearSensitiveData(); + completion.fail(errCode); + } + }).start(); + } + + static class PrepareTpmStateHostFileContext { + String hostUuid; + String vmUuid; + + // whether the NvRam is on the same host as before + boolean sameHost = false; + VmHostFileVO tpmStateFile; + } + + static class PrepareTpmResourceContext { + boolean enableKeyProvider; + String tpmUuid; + String backupFileUuid; + String providerUuid; + Integer keyVersion; + String dekBase64; + boolean instantiateForNewVm; + + void clearSensitiveData() { + dekBase64 = null; + } + } + + @Override + public void preReleaseVmResource(VmInstanceSpec spec, Completion completion) { + TpmSpec tpmSpec = spec.getDevicesSpec() == null ? null : spec.getDevicesSpec().getTpm(); + if (tpmSpec == null || !tpmSpec.isResourceKeyCreatedNew()) { + completion.success(); + return; + } + + ResourceKeyResult result = new ResourceKeyResult(); + result.setResourceUuid(tpmSpec.getTpmUuid()); + result.setResourceType(TpmVO.class.getSimpleName()); + result.setKeyProviderUuid(tpmSpec.getResourceKeyProviderUuid()); + result.setCreatedNewKey(true); + + resourceKeyManager.rollbackCreatedKey(result, new Completion(completion) { + @Override + public void success() { + clearRollbackInfo(spec); + completion.success(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to rollback TPM resource key for tpm[uuid:%s]: %s", + tpmSpec.getTpmUuid(), errorCode != null ? errorCode.getDetails() : "")); + clearRollbackInfo(spec); + completion.success(); + } + }); + } + + @Override + public void vmJustBeforeDeleteFromDb(VmInstanceInventory inv) { + String tpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, inv.getUuid()) + .select(TpmVO_.uuid) + .findValue(); + if (tpmUuid == null) { + return; + } + + // Delete host secrets while TPM row and key version are still resolvable. + // RemoveTpm may skip or fail (e.g. VM not in Stopped), and callback order may change. + Integer keyVersion = resourceKeyBackend.findKeyVersionByTpm(tpmUuid); + Set hostUuids = new HashSet<>(); + List tpmStateFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, inv.getUuid()) + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) + .list(); + for (VmHostFileVO f : tpmStateFiles) { + if (StringUtils.isNotBlank(f.getHostUuid())) { + hostUuids.add(f.getHostUuid()); + } + } + if (StringUtils.isNotBlank(inv.getHostUuid())) { + hostUuids.add(inv.getHostUuid()); + } + if (StringUtils.isNotBlank(inv.getLastHostUuid())) { + hostUuids.add(inv.getLastHostUuid()); + } + for (String hostUuid : hostUuids) { + deleteHostSecretBestEffort(hostUuid, inv.getUuid(), keyVersion, "vm-just-before-delete-from-db"); + } + + RemoveTpmMsg removeMsg = new RemoveTpmMsg(); + removeMsg.setVmInstanceUuid(inv.getUuid()); + removeMsg.setTpmUuid(tpmUuid); + bus.makeTargetServiceIdByResourceUuid(removeMsg, SERVICE_ID, removeMsg.getTpmUuid()); + MessageReply reply = bus.call(removeMsg); + if (!reply.isSuccess()) { + logger.warn(String.format("failed to remove TPM[uuid:%s] of VM[uuid:%s], error: %s", + tpmUuid, inv.getUuid(), reply.getError())); + } + } + + private void clearRollbackInfo(VmInstanceSpec spec) { + if (spec.getDevicesSpec() == null || spec.getDevicesSpec().getTpm() == null) { + return; + } + spec.getDevicesSpec().getTpm().setResourceKeyCreatedNew(false); + spec.getDevicesSpec().getTpm().setResourceKeyProviderUuid(null); + } + + private String findSourceTpmUuidFromSnapshotTpmBackupFile(String tpmBackupFileUuid) { + if (StringUtils.isBlank(tpmBackupFileUuid)) { + return null; + } + VmHostBackupFileVO bf = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.uuid, tpmBackupFileUuid) + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) + .find(); + if (bf == null || StringUtils.isBlank(bf.getResourceUuid())) { + return null; + } + String sourceVmUuid = Q.New(VolumeSnapshotGroupVO.class) + .select(VolumeSnapshotGroupVO_.vmInstanceUuid) + .eq(VolumeSnapshotGroupVO_.uuid, bf.getResourceUuid()) + .findValue(); + if (StringUtils.isBlank(sourceVmUuid)) { + return null; + } + return Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, sourceVmUuid) + .select(TpmVO_.uuid) + .findValue(); + } + + @Override + public void preMigrateVm(VmInstanceInventory inv, String destHostUuid, Completion completion) { + if (inv == null || StringUtils.isBlank(destHostUuid)) { + completion.success(); + return; + } + String srcHostUuid = inv.getHostUuid(); + if (StringUtils.isBlank(srcHostUuid)) { + completion.success(); + return; + } + try { + VtpmMigratePreAgentContext ctx = new VtpmMigratePreAgentContext(inv.getUuid(), srcHostUuid, destHostUuid); + ctx.setEnableKeyProvider(!VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class)); + prepareVtpmSecretOnHostsBeforeMigrate(ctx); + completion.success(); + } catch (OperationFailureException e) { + completion.fail(e.getErrorCode()); + } + } + + private void prepareVtpmSecretOnHostsBeforeMigrate(VtpmMigratePreAgentContext ctx) { + String tpmUuid = VmTpmManager.findTpmUuidForVmOrNull(ctx.getVmUuid()); + if (StringUtils.isBlank(tpmUuid)) { + return; + } + if (!ctx.isEnableKeyProvider()) { + return; + } + ctx.setTpmUuid(tpmUuid); + ctx.setProviderUuid(resourceKeyBackend.findKeyProviderUuidByTpm(tpmUuid)); + ctx.setProviderName(resourceKeyBackend.findKeyProviderNameByTpm(tpmUuid)); + if (StringUtils.isBlank(ctx.getProviderUuid()) && StringUtils.isBlank(ctx.getProviderName())) { + throw new OperationFailureException(operr("missing TPM resource key binding for tpm[uuid:%s] before migrate", tpmUuid)); + } + ctx.setKeyVersion(resourceKeyBackend.findKeyVersionByTpm(tpmUuid)); + if (ctx.getKeyVersion() == null) { + throw new OperationFailureException(operr("cannot find keyVersion for tpm[uuid:%s] before migrate", tpmUuid)); + } + + GetOrCreateResourceKeyContext keyCtx = new GetOrCreateResourceKeyContext(); + keyCtx.setResourceUuid(ctx.getTpmUuid()); + keyCtx.setResourceType(TpmVO.class.getSimpleName()); + keyCtx.setKeyProviderUuid(ctx.getProviderUuid()); + keyCtx.setKeyProviderName(ctx.getProviderName()); + keyCtx.setPurpose("vtpm"); + ResourceKeyResult result = resourceKeyManager.getKey(keyCtx); + if (StringUtils.isBlank(result.getDekBase64())) { + throw new OperationFailureException(operr("missing DEK for tpm[uuid:%s] after getKey before migrate", ctx.getTpmUuid())); + } + ctx.setResourceKeyResult(result); + + SecretHostGetMsg getMsg = new SecretHostGetMsg(); + getMsg.setHostUuid(ctx.getSrcHostUuid()); + getMsg.setVmUuid(ctx.getVmUuid()); + getMsg.setPurpose("vtpm"); + getMsg.setKeyVersion(ctx.getKeyVersion()); + getMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(getMsg, HostConstant.SERVICE_ID, getMsg.getHostUuid()); + MessageReply getReply = bus.call(getMsg); + if (!getReply.isSuccess()) { + throw new OperationFailureException(getReply.getError() != null ? getReply.getError() + : operr("get secret on source host failed before migrate")); + } + SecretHostGetReply getR = getReply.castReply(); + if (StringUtils.isBlank(getR.getSecretUuid())) { + throw new OperationFailureException(operr("failed to get source secret uuid before migrate: empty secretUuid")); + } + ctx.setSourceSecretUuid(getR.getSecretUuid()); + + ResourceKeyResult keyResult = ctx.getResourceKeyResult(); + if (keyResult == null || StringUtils.isBlank(keyResult.getDekBase64())) { + throw new OperationFailureException(operr("missing DEK for tpm[uuid:%s] before ensure secret on destination", ctx.getTpmUuid())); + } + SecretHostDefineMsg defMsg = new SecretHostDefineMsg(); + defMsg.setHostUuid(ctx.getDstHostUuid()); + defMsg.setVmUuid(ctx.getVmUuid()); + defMsg.setDekBase64(keyResult.getDekBase64()); + defMsg.setPurpose("vtpm"); + defMsg.setKeyVersion(ctx.getKeyVersion()); + defMsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + defMsg.setSecretUuid(ctx.getSourceSecretUuid()); + defMsg.setDescription(String.format("Define secret for VM %s before live migration", ctx.getVmUuid())); + bus.makeTargetServiceIdByResourceUuid(defMsg, HostConstant.SERVICE_ID, defMsg.getHostUuid()); + MessageReply defReply = bus.call(defMsg); + if (!defReply.isSuccess()) { + throw new OperationFailureException(defReply.getError()); + } + } + + @Override + public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid, NoErrorCompletion completion) { + String vmUuid = inv == null ? null : inv.getUuid(); + String destHostUuid = inv == null ? null : inv.getHostUuid(); + if (StringUtils.isBlank(vmUuid) || StringUtils.isBlank(srcHostUuid) || srcHostUuid.equals(destHostUuid)) { + completion.done(); + return; + } + + if (!isVmCurrentlyOnExpectedHost(vmUuid, destHostUuid)) { + completion.done(); + return; + } + + Integer keyVersion = findTpmKeyVersionByVmUuid(vmUuid); + deleteHostSecretBestEffort(srcHostUuid, vmUuid, keyVersion, "after-migrate"); + completion.done(); + } + + @Override + public void vmStateChanged(VmInstanceInventory vm, VmInstanceState oldState, VmInstanceState newState) { + String vmUuid = vm == null ? null : vm.getUuid(); + if (StringUtils.isBlank(vmUuid)) { + logger.info(String.format("vmStateChanged skip: vmUuid is blank, oldState=%s, newState=%s", oldState, newState)); + return; + } + + // Record source host when storage migration starts. In some end-state events (e.g. VolumeMigrating->Stopped), + // inventory host fields may not carry both src/dst host values reliably. + if (newState == VmInstanceState.VolumeMigrating) { + String srcHostUuid = vm.getLastHostUuid(); + if (StringUtils.isBlank(srcHostUuid)) { + srcHostUuid = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + } + if (StringUtils.isNotBlank(srcHostUuid)) { + volumeMigratingSourceHostCache.put(vmUuid, srcHostUuid); + logger.info(String.format( + "vmStateChanged cache volume-migrating src host: vm[uuid:%s], oldState=%s, newState=%s, srcHostUuid=%s", + vmUuid, oldState, newState, srcHostUuid)); + } else { + logger.info(String.format( + "vmStateChanged skip cache: source host is blank, vm[uuid:%s], oldState=%s, newState=%s", + vmUuid, oldState, newState)); + } + return; + } + + if (oldState != VmInstanceState.VolumeMigrating) { + return; + } + + String srcHostUuid = volumeMigratingSourceHostCache.remove(vmUuid); + if (StringUtils.isBlank(srcHostUuid)) { + logger.info(String.format( + "vmStateChanged skip delete: no cached source host, vm[uuid:%s], oldState=%s, newState=%s", + vmUuid, oldState, newState)); + return; + } + + String destHostUuid = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + if (StringUtils.isBlank(destHostUuid)) { + destHostUuid = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.lastHostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + } + if (StringUtils.isBlank(destHostUuid) || srcHostUuid.equals(destHostUuid)) { + logger.info(String.format( + "vmStateChanged skip delete: invalid host mapping, vm[uuid:%s], oldState=%s, newState=%s, srcHostUuid=%s, destHostUuid=%s", + vmUuid, oldState, newState, srcHostUuid, destHostUuid)); + return; + } + + Integer keyVersion = findTpmKeyVersionByVmUuid(vmUuid); + logger.info(String.format( + "vmStateChanged trigger delete: vm[uuid:%s], srcHostUuid=%s, destHostUuid=%s, keyVersion=%s, reason=volume-migrated-host-change", + vmUuid, srcHostUuid, destHostUuid, keyVersion)); + deleteHostSecretBestEffort(srcHostUuid, vmUuid, keyVersion, "volume-migrated-host-change"); + } + + @Override + public void vmAfterExpunge(VmInstanceInventory vm) { + String vmUuid = vm.getUuid(); + Integer keyVersion = findTpmKeyVersionByVmUuid(vmUuid); + + java.util.Set hostUuids = new java.util.HashSet<>(); + if (StringUtils.isNotBlank(vm.getHostUuid())) { + hostUuids.add(vm.getHostUuid()); + } + if (StringUtils.isNotBlank(vm.getLastHostUuid())) { + hostUuids.add(vm.getLastHostUuid()); + } + + if (hostUuids.isEmpty()) { + return; + } + + for (String hostUuid : hostUuids) { + deleteHostSecretBestEffort(hostUuid, vmUuid, keyVersion, "expunge"); + } + } + + private Integer findTpmKeyVersionByVmUuid(String vmUuid) { + if (StringUtils.isBlank(vmUuid)) { + return null; + } + String tpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .select(TpmVO_.uuid) + .findValue(); + return tpmUuid == null ? null : resourceKeyBackend.findKeyVersionByTpm(tpmUuid); + } + + private boolean isVmCurrentlyOnExpectedHost(String vmUuid, String expectedHostUuid) { + if (StringUtils.isBlank(vmUuid) || StringUtils.isBlank(expectedHostUuid)) { + return false; + } + + String currentHostUuid = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.hostUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findValue(); + return expectedHostUuid.equals(currentHostUuid); + } + + private static boolean isVtpmSecretNotFoundOnHost(ErrorCode errorCode) { + if (SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND.equals(errorCode.getCode())) { + return true; + } + String details = errorCode.getDetails(); + return details != null && details.contains(SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND); + } + + private void deleteHostSecretBestEffort(String hostUuid, String vmUuid, Integer keyVersion, String reason) { + if (StringUtils.isBlank(hostUuid) || StringUtils.isBlank(vmUuid) || keyVersion == null) { + logger.info(String.format( + "skip delete host secret: reason=%s, hostUuid=%s, vmUuid=%s, keyVersion=%s", + reason, hostUuid, vmUuid, keyVersion)); + return; + } + + logger.info(String.format( + "send delete host secret: reason=%s, hostUuid=%s, vmUuid=%s, keyVersion=%s", + reason, hostUuid, vmUuid, keyVersion)); + SecretHostDeleteMsg dmsg = new SecretHostDeleteMsg(); + dmsg.setHostUuid(hostUuid); + dmsg.setVmUuid(vmUuid); + dmsg.setPurpose("vtpm"); + dmsg.setKeyVersion(keyVersion); + dmsg.setUsageInstance(HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(dmsg, new CloudBusCallBack(null) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + ErrorCode err = reply.getError(); + String errMsg = err != null && err.getDetails() != null ? err.getDetails() : "unknown error"; + logger.warn(String.format( + "best-effort delete host secret failed on %s for vm[uuid:%s], host[uuid:%s]: %s", + reason, vmUuid, hostUuid, errMsg)); + } + } + }); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java new file mode 100644 index 00000000000..6152dff890a --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/KvmTpmManager.java @@ -0,0 +1,932 @@ +package org.zstack.kvm.tpm; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.compute.vm.devices.VmTpmManager; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.cloudbus.MessageSafe; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.db.SQLBatch; +import org.zstack.core.thread.ChainTask; +import org.zstack.core.thread.SyncTaskChain; +import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.workflow.SimpleFlowChain; +import org.zstack.header.AbstractService; +import org.zstack.header.core.Completion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.core.workflow.Flow; +import org.zstack.header.core.workflow.FlowDoneHandler; +import org.zstack.header.core.workflow.FlowErrorHandler; +import org.zstack.header.core.workflow.FlowRollback; +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.host.HostConstant; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.Message; +import org.zstack.header.message.MessageReply; +import org.zstack.header.secret.SecretHostDeleteMsg; +import org.zstack.header.tpm.api.APIAddTpmEvent; +import org.zstack.header.tpm.api.APIAddTpmMsg; +import org.zstack.header.tpm.api.APIGetTpmCapabilityMsg; +import org.zstack.header.tpm.api.APIGetTpmCapabilityReply; +import org.zstack.header.tpm.api.APIRemoveTpmEvent; +import org.zstack.header.tpm.api.APIRemoveTpmMsg; +import org.zstack.header.tpm.api.APIUpdateTpmMsg; +import org.zstack.header.tpm.entity.TpmCapabilityView; +import org.zstack.header.tpm.entity.TpmInventory; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.tpm.message.AddTpmMsg; +import org.zstack.header.tpm.message.AddTpmReply; +import org.zstack.header.tpm.message.RemoveTpmMsg; +import org.zstack.header.tpm.message.RemoveTpmReply; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.ResetVmTpmMsg; +import org.zstack.header.vm.additions.ResetVmTpmReply; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileInventory; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.header.vm.additions.VmHostFileVO_; +import org.zstack.header.vm.additions.VmHostFileOperation; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.kvm.KVMConstant; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.kvm.KvmCommandSender; +import org.zstack.kvm.KvmResponseWrapper; +import org.zstack.kvm.efi.KvmSecureBootExtensions; +import org.zstack.kvm.tpm.message.CloneVmTpmMsg; +import org.zstack.kvm.tpm.message.CloneVmTpmReply; +import org.zstack.resourceconfig.ResourceConfig; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.zstack.compute.vm.VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE; +import static org.zstack.core.Platform.err; +import static org.zstack.core.Platform.operr; +import static org.zstack.header.errorcode.SysErrors.NOT_SUPPORTED; +import static org.zstack.header.tpm.TpmConstants.*; +import static org.zstack.header.tpm.TpmErrors.VM_STATE_ERROR; +import static org.zstack.kvm.KVMSystemTags.EDK_RPM_TOKEN; +import static org.zstack.kvm.KVMSystemTags.SWTPM_VERSION; +import static org.zstack.kvm.KVMSystemTags.SWTPM_VERSION_TOKEN; +import static org.zstack.kvm.KVMSystemTags.VM_EDK; +import static org.zstack.utils.CollectionDSL.list; +import static org.zstack.utils.CollectionUtils.transform; + +public class KvmTpmManager extends AbstractService { + private static final CLogger logger = Utils.getLogger(KvmTpmManager.class); + + @Autowired + private CloudBus bus; + @Autowired + private ThreadFacade threadFacade; + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private VmTpmManager vmTpmManager; + @Autowired + private TpmEncryptedResourceKeyBackend tpmKeyBackend; + @Autowired + private KvmSecureBootExtensions secureBootExtensions; + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getId() { + return bus.makeLocalServiceId(SERVICE_ID); + } + + private String tpmQueueSyncSignature(String vmUuid) { + return String.format("tpm-queue-sync-%s", vmUuid); + } + + @MessageSafe + public void handleMessage(Message msg) { + if (msg instanceof APIMessage) { + handleApiMessage((APIMessage) msg); + } else { + handleLocalMessage(msg); + } + } + + private void handleLocalMessage(Message msg) { + if (msg instanceof AddTpmMsg) { + handle((AddTpmMsg) msg); + } else if (msg instanceof RemoveTpmMsg) { + handle((RemoveTpmMsg) msg); + } else if (msg instanceof CloneVmTpmMsg) { + handle((CloneVmTpmMsg) msg); + } else if (msg instanceof ResetVmTpmMsg) { + handle((ResetVmTpmMsg) msg); + } else { + bus.dealWithUnknownMessage(msg); + } + } + + private void handleApiMessage(APIMessage msg) { + if (msg instanceof APIGetTpmCapabilityMsg) { + handle((APIGetTpmCapabilityMsg) msg); + } else if (msg instanceof APIAddTpmMsg) { + handle((APIAddTpmMsg) msg); + } else if (msg instanceof APIRemoveTpmMsg) { + handle((APIRemoveTpmMsg) msg); + } else if (msg instanceof APIUpdateTpmMsg) { + handle((APIUpdateTpmMsg) msg); + } else { + bus.dealWithUnknownMessage(msg); + } + } + + private void handle(AddTpmMsg msg) { + AddTpmReply reply = new AddTpmReply(); + threadFacade.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + AddTpmToVmContext context = AddTpmToVmContext.valueOf(msg); + addTpmToVm(context, new Completion(chain, msg) { + @Override + public void success() { + chain.next(); + TpmVO vo = Q.New(TpmVO.class) + .eq(TpmVO_.uuid, msg.getTpmUuid()) + .find(); + reply.setInventory(TpmInventory.valueOf(vo)); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + chain.next(); + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + public String getSyncSignature() { + return tpmQueueSyncSignature(msg.getVmInstanceUuid()); + } + + @Override + public String getName() { + return "queue-of-add-tpm-to-vm-" + msg.getVmInstanceUuid(); + } + }); + } + + static class AddTpmToVmContext { + String keyProviderUuid; + String vmInstanceUuid; + String tpmUuid; + + boolean tpmCreated; + boolean keyProviderAttached; + String createdTpmUuid; + + static AddTpmToVmContext valueOf(AddTpmMsg msg) { + AddTpmToVmContext context = new AddTpmToVmContext(); + context.keyProviderUuid = msg.getKeyProviderUuid(); + context.vmInstanceUuid = msg.getVmInstanceUuid(); + context.tpmUuid = msg.getTpmUuid(); + return context; + } + } + + private void addTpmToVm(AddTpmToVmContext context, Completion completion) { + SimpleFlowChain.of("add-tpm-to-vm-" + context.vmInstanceUuid) + .then(Flow.of("check-vm-status") + .handle(trigger -> { + VmInstanceVO vm = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, context.vmInstanceUuid) + .find(); + + if (!SUPPORT_VM_STATES_FOR_TPM_OPERATION.contains(vm.getState())) { + trigger.fail(err(VM_STATE_ERROR, + "The current VM state does not support adding TPM operations") + .withOpaque("support.vm.state", SUPPORT_VM_STATES_FOR_TPM_OPERATION)); + return; + } + trigger.next(); + }) + .build()) + .then(Flow.of("create-tpm-db-records") + .handle(trigger -> { + TpmVO tpm = vmTpmManager.persistTpmVO(context.tpmUuid, context.vmInstanceUuid); + context.createdTpmUuid = tpm.getUuid(); + context.tpmCreated = true; + trigger.next(); + }) + .rollback(trigger -> { + if (context.tpmCreated && context.createdTpmUuid != null) { + vmTpmManager.deleteTpmVO(context.createdTpmUuid); + } + trigger.rollback(); + }) + .build()) + .then(Flow.of("attach-key-provider-to-tpm") + .skipIf(data -> VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class)) + .handle(trigger -> { + if (context.keyProviderUuid != null) { + tpmKeyBackend.attachKeyProviderToTpm(context.createdTpmUuid, context.keyProviderUuid); + context.keyProviderAttached = true; + } + trigger.next(); + }) + .rollback(trigger -> { + if (context.keyProviderAttached && context.createdTpmUuid != null) { + tpmKeyBackend.detachKeyProviderFromTpm(context.createdTpmUuid); + } + trigger.rollback(); + }) + .build()) + .propagateExceptionTo(completion) + .done(completion::success) + .error(completion::fail) + .start(); + } + + private void handle(RemoveTpmMsg msg) { + RemoveTpmReply reply = new RemoveTpmReply(); + threadFacade.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + RemoveTpmFromVmContext context = RemoveTpmFromVmContext.valueOf(msg); + removeTpmFromVm(context, new Completion(chain, msg) { + @Override + public void success() { + chain.next(); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + chain.next(); + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + public String getSyncSignature() { + return tpmQueueSyncSignature(msg.getVmInstanceUuid()); + } + + @Override + public String getName() { + return "queue-of-remove-tpm-from-vm-" + msg.getVmInstanceUuid(); + } + }); + } + + static class RemoveTpmFromVmContext { + String vmInstanceUuid; + String tpmUuid; + Integer keyVersion; + + // enable when TPM delete/VM delete operation + boolean force; + + List hostFiles; + + static RemoveTpmFromVmContext valueOf(RemoveTpmMsg msg) { + RemoveTpmFromVmContext context = new RemoveTpmFromVmContext(); + context.vmInstanceUuid = msg.getVmInstanceUuid(); + context.tpmUuid = msg.getTpmUuid(); + return context; + } + } + + private void removeTpmFromVm(RemoveTpmFromVmContext context, Completion completion) { + SimpleFlowChain.of("remove-tpm-from-vm-" + context.vmInstanceUuid) + .then(Flow.of("check-vm-status") + .skipIf(data -> context.force) + .handle(trigger -> { + VmInstanceVO vm = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, context.vmInstanceUuid) + .find(); + + if (!SUPPORT_VM_STATES_FOR_TPM_OPERATION.contains(vm.getState())) { + trigger.fail(err(VM_STATE_ERROR, + "The current VM state does not support removing TPM operations") + .withOpaque("support.vm.state", SUPPORT_VM_STATES_FOR_TPM_OPERATION)); + return; + } + trigger.next(); + }) + .build()) + .then(Flow.of("collect-vm-host-files") + .handle(trigger -> { + context.keyVersion = tpmKeyBackend.findKeyVersionByTpm(context.tpmUuid); + // DO NOT delete NvRam type VmHostFile: Maybe secure boot or other component related. + context.hostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, context.vmInstanceUuid) + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) + .list(); + trigger.next(); + }) + .build()) + .then(Flow.of("send-delete-commands-to-hosts") + .skipIf(data -> context.hostFiles.isEmpty()) + .handle(trigger -> { + Map> filesByHost = new HashMap<>(); + for (VmHostFileVO file : context.hostFiles) { + filesByHost.computeIfAbsent(file.getHostUuid(), k -> new ArrayList<>()).add(file); + } + + new While<>(filesByHost.entrySet()).each((entry, whileCompletion) -> { + String hostUuid = entry.getKey(); + List files = entry.getValue(); + + KVMAgentCommands.WriteVmHostFileContentCmd cmd = new KVMAgentCommands.WriteVmHostFileContentCmd(); + List fileTOs = new ArrayList<>(); + for (VmHostFileVO file : files) { + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(file.getPath()); + to.setType(file.getType().toString()); + to.setOperation(VmHostFileOperation.Delete.toString()); + fileTOs.add(to); + } + cmd.setHostFiles(fileTOs); + + new KvmCommandSender(hostUuid).send(cmd, KVMConstant.WRITE_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.WriteVmHostFileContentResponse rsp = + wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); + return rsp.isSuccess() ? null : operr("failed to delete host files on host[uuid=%s]", hostUuid); + }, new ReturnValueCompletion(whileCompletion) { + @Override + public void success(KvmResponseWrapper wrapper) { + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to delete host files on host[uuid=%s], but continuing with DB cleanup: %s", + hostUuid, errorCode.getDetails())); + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + }); + }) + .build()) + .then(Flow.of("delete-host-secret") + .skipIf(data -> context.keyVersion == null) + .handle(trigger -> { + Set hostUuids = new HashSet<>(); + for (VmHostFileVO file : context.hostFiles) { + hostUuids.add(file.getHostUuid()); + } + if (hostUuids.isEmpty()) { + addVmCurrentAndLastHostUuidsForSecretDelete(hostUuids, context.vmInstanceUuid); + } + if (hostUuids.isEmpty()) { + trigger.next(); + return; + } + + new While<>(new ArrayList<>(hostUuids)).each((hostUuid, whileCompletion) -> { + SecretHostDeleteMsg dmsg = new SecretHostDeleteMsg(); + dmsg.setHostUuid(hostUuid); + dmsg.setVmUuid(context.vmInstanceUuid); + dmsg.setPurpose("vtpm"); + dmsg.setKeyVersion(context.keyVersion); + dmsg.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(dmsg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + ErrorCode err = reply.getError(); + String errMsg = err != null && err.getDetails() != null ? err.getDetails() : "unknown error"; + logger.warn(String.format("failed to delete host secret on host[uuid:%s] for vm[uuid:%s], continue cleanup: %s", + hostUuid, context.vmInstanceUuid, errMsg)); + } + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + }); + }) + .build()) + .then(Flow.of("detach-resource-key") + .handle(trigger -> { + tpmKeyBackend.detachKeyProviderFromTpm(context.tpmUuid); + trigger.next(); + }) + .build()) + .then(Flow.of("remove-db-records") + .handle(trigger -> { + new SQLBatch() { + @Override + protected void scripts() { + sql(TpmVO.class) + .eq(TpmVO_.uuid, context.tpmUuid) + .delete(); + sql(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, context.vmInstanceUuid) + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) + .delete(); + sql(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, context.vmInstanceUuid) + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) + .delete(); + } + }.execute(); + trigger.next(); + }) + .build()) + .propagateExceptionTo(completion) + .done(completion::success) + .error(completion::fail) + .start(); + } + + @SuppressWarnings("rawtypes") + private void handle(CloneVmTpmMsg msg) { + CloneVmTpmReply reply = new CloneVmTpmReply(); + + String originTpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, msg.getSrcVmUuid()) + .select(TpmVO_.uuid) + .findValue(); + if (originTpmUuid == null) { + bus.reply(msg, reply); + return; + } + + SimpleFlowChain chain = new SimpleFlowChain(); + chain.setName("clone-VM-TPM"); + chain.then(new Flow() { + String __name__ = "persist-TPM-VO"; + + @Override + public void run(FlowTrigger trigger, Map data) { + reply.setInventories(new ArrayList<>()); + for (String dstVmUuid : msg.getDstVmUuidList()) { + TpmVO dstTpm = vmTpmManager.persistTpmVO(null, dstVmUuid); + reply.getInventories().add(TpmInventory.valueOf(dstTpm)); + } + trigger.next(); + } + + @Override + public void rollback(FlowRollback trigger, Map data) { + if (CollectionUtils.isEmpty(reply.getInventories())) { + trigger.rollback(); + return; + } + + new While<>(reply.getInventories()).each((tpm, whileCompletion) -> { + RemoveTpmFromVmContext removeContext = new RemoveTpmFromVmContext(); + removeContext.vmInstanceUuid = tpm.getVmInstanceUuid(); + removeContext.tpmUuid = tpm.getUuid(); + removeContext.force = true; + removeTpmFromVm(removeContext, new Completion(whileCompletion) { + @Override + public void success() { + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("failed to delete tpm for VM[%s] but still continue: %s", + tpm.getVmInstanceUuid(), errorCode.getReadableDetails())); + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.rollback(); + } + }); + } + }).then(new NoRollbackFlow() { + String __name__ = "clone-encrypted-resource-key-if-needed"; + + @Override + public void run(FlowTrigger trigger, Map data) { + boolean resetTpm; + if (msg.getResetTpm() == null) { + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity()); + resetTpm = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class); + } else { + resetTpm = msg.getResetTpm(); + } + + new While<>(reply.getInventories()).each((inventory, whileCompletion) -> { + TpmEncryptedResourceKeyBackend.CloneEncryptedResourceKeyContext context = + new TpmEncryptedResourceKeyBackend.CloneEncryptedResourceKeyContext(); + context.srcTpmUuid = originTpmUuid; + context.dstTpmUuid = inventory.getUuid(); + context.resetTpm = resetTpm; + tpmKeyBackend.cloneEncryptedResourceKey(context, new Completion(whileCompletion) { + @Override + public void success() { + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + whileCompletion.addError(errorCode); + whileCompletion.allDone(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (errorCodeList.isEmpty()) { + trigger.next(); + return; + } + trigger.fail(operr("Failed to clone encrypted resource key") + .withOpaque("src.tpm.uuid", originTpmUuid) + .withCause(errorCodeList)); + } + }); + } + }).done(new FlowDoneHandler(msg) { + @Override + public void handle(Map data) { + bus.reply(msg, reply); + } + }).error(new FlowErrorHandler(msg) { + @Override + public void handle(ErrorCode errCode, Map data) { + reply.setError(errCode); + bus.reply(msg, reply); + } + }).start(); + } + + static class ResetVmTpmContext { + String vmInstanceUuid; + Integer keyVersion; + + List hostFiles; + VmHostFileVO hostFileToDeleteLast; + List hostFileUuidListDeleteSuccessfully = new ArrayList<>(); + ErrorCodeList errorsOnSendCmd = new ErrorCodeList(); + + static ResetVmTpmContext valueOf(ResetVmTpmMsg msg) { + ResetVmTpmContext context = new ResetVmTpmContext(); + context.vmInstanceUuid = msg.getVmInstanceUuid(); + return context; + } + } + + private void handle(ResetVmTpmMsg msg) { + ResetVmTpmReply reply = new ResetVmTpmReply(); + threadFacade.chainSubmit(new ChainTask(msg) { + @Override + public void run(SyncTaskChain chain) { + ResetVmTpmContext context = ResetVmTpmContext.valueOf(msg); + resetVmTpm(context, new Completion(chain, msg) { + @Override + public void success() { + chain.next(); + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + chain.next(); + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + + @Override + public String getSyncSignature() { + return tpmQueueSyncSignature(msg.getVmInstanceUuid()); + } + + @Override + public String getName() { + return "queue-of-reset-tpm-from-vm-" + msg.getVmInstanceUuid(); + } + }); + } + + private void resetVmTpm(ResetVmTpmContext context, Completion completion) { + String vmUuid = context.vmInstanceUuid; + String tpmUuid = Q.New(TpmVO.class) + .eq(TpmVO_.vmInstanceUuid, vmUuid) + .select(TpmVO_.uuid) + .findValue(); + if (tpmUuid != null) { + context.keyVersion = tpmKeyBackend.findKeyVersionByTpm(tpmUuid); + } + + SimpleFlowChain.of("reset-vm-tpm-" + vmUuid) + .then(Flow.of("collect-vm-host-files") + .handle(trigger -> { + context.hostFiles = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.type, VmHostFileType.TpmState) + .orderByAsc(VmHostFileVO_.lastOpDate) + .list(); + if (!context.hostFiles.isEmpty()) { + // We should delete it in last turn: + context.hostFileToDeleteLast = context.hostFiles.get(context.hostFiles.size() - 1); + context.hostFiles.remove(context.hostFiles.size() - 1); + } + trigger.next(); + }) + .build()) + .then(Flow.of("send-delete-commands-to-hosts-exclude-last-modified") + .skipIf(data -> context.hostFiles.isEmpty()) + .handle(trigger -> { + Map> filesByHost = new HashMap<>(); + for (VmHostFileVO file : context.hostFiles) { + filesByHost.computeIfAbsent(file.getHostUuid(), k -> new ArrayList<>()).add(file); + } + + new While<>(filesByHost.entrySet()).each((entry, whileCompletion) -> { + List fileTOs = new ArrayList<>(); + for (VmHostFileVO file : entry.getValue()) { + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(file.getPath()); + to.setType(file.getType().toString()); + to.setOperation(VmHostFileOperation.Delete.toString()); + fileTOs.add(to); + } + + KvmSecureBootExtensions.RewriteVmHostFilesContext ctx = + new KvmSecureBootExtensions.RewriteVmHostFilesContext(); + ctx.hostUuid = entry.getKey(); + ctx.hostFiles = fileTOs; + + secureBootExtensions.rewriteVmHostFiles(ctx, new Completion(whileCompletion) { + @Override + public void success() { + context.hostFileUuidListDeleteSuccessfully.addAll( + transform(entry.getValue(), VmHostFileVO::getUuid)); + whileCompletion.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + context.errorsOnSendCmd.add(errorCode.withOpaque("host.uuid", entry.getKey())); + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + }); + }) + .build()) + .then(Flow.of("remove-db-records") + .skipIf(data -> context.hostFileUuidListDeleteSuccessfully.isEmpty()) + .handle(trigger -> { + SQL.New(VmHostFileVO.class) + .in(VmHostFileVO_.uuid, context.hostFileUuidListDeleteSuccessfully) + .delete(); + trigger.next(); + }) + .build()) + .then(Flow.of("check-if-any-error-in-command-sending") + .handle(trigger -> { + // If any host failed to delete, abort the chain to preserve + // the last-modified TPM record as a recovery point. + if (context.errorsOnSendCmd.hasError()) { + if (context.errorsOnSendCmd.size() == 1) { + trigger.fail(context.errorsOnSendCmd.getCauses().get(0)); + } else { + trigger.fail(operr("failed to delete TPM files on multiple hosts") + .withOpaque("vm.uuid", vmUuid) + .withCause(context.errorsOnSendCmd.getCauses())); + } + return; + } + trigger.next(); + }) + .build()) + .then(Flow.of("send-delete-commands-to-hosts-for-last-modified") + .skipIf(data -> context.hostFileToDeleteLast == null) + .handle(trigger -> { + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(context.hostFileToDeleteLast.getPath()); + to.setType(context.hostFileToDeleteLast.getType().toString()); + to.setOperation(VmHostFileOperation.Delete.toString()); + + KvmSecureBootExtensions.RewriteVmHostFilesContext ctx = + new KvmSecureBootExtensions.RewriteVmHostFilesContext(); + ctx.hostUuid = context.hostFileToDeleteLast.getHostUuid(); + ctx.hostFiles = list(to); + + secureBootExtensions.rewriteVmHostFiles(ctx, new Completion(trigger) { + @Override + public void success() { + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + trigger.fail(errorCode.withOpaque("host.uuid", ctx.hostUuid)); + } + }); + }) + .build()) + .then(Flow.of("remove-db-records-for-remains") + .skipIf(data -> context.hostFileToDeleteLast == null) + .handle(trigger -> { + new SQLBatch() { + @Override + protected void scripts() { + sql(VmHostFileVO.class) + .eq(VmHostFileVO_.uuid, context.hostFileToDeleteLast.getUuid()) + .delete(); + sql(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, vmUuid) + .eq(VmHostBackupFileVO_.type, VmHostFileType.TpmState) + .delete(); + } + }.execute(); + trigger.next(); + }) + .build()) + .then(Flow.of("delete-host-secret") + .handle(trigger -> { + if (context.keyVersion == null) { + trigger.next(); + return; + } + Set hostUuids = new HashSet<>(); + for (VmHostFileVO file : context.hostFiles) { + hostUuids.add(file.getHostUuid()); + } + if (context.hostFileToDeleteLast != null) { + hostUuids.add(context.hostFileToDeleteLast.getHostUuid()); + } + if (hostUuids.isEmpty()) { + addVmCurrentAndLastHostUuidsForSecretDelete(hostUuids, vmUuid); + } + if (hostUuids.isEmpty()) { + trigger.next(); + return; + } + + new While<>(new ArrayList<>(hostUuids)).each((hostUuid, whileCompletion) -> { + SecretHostDeleteMsg dmsg = new SecretHostDeleteMsg(); + dmsg.setHostUuid(hostUuid); + dmsg.setVmUuid(vmUuid); + dmsg.setPurpose("vtpm"); + dmsg.setKeyVersion(context.keyVersion); + dmsg.setUsageInstance(KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM); + bus.makeTargetServiceIdByResourceUuid(dmsg, HostConstant.SERVICE_ID, hostUuid); + bus.send(dmsg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + ErrorCode err = reply.getError(); + String errMsg = err != null && err.getDetails() != null ? err.getDetails() : "unknown error"; + logger.warn(String.format("failed to delete host secret on host[uuid:%s] for vm[uuid:%s], continue reset: %s", + hostUuid, vmUuid, errMsg)); + } + whileCompletion.done(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + }); + }) + .build()) + .propagateExceptionTo(completion) + .done(completion::success) + .error(completion::fail) + .start(); + } + + private static void addVmCurrentAndLastHostUuidsForSecretDelete(Set hostUuids, String vmInstanceUuid) { + if (vmInstanceUuid == null) { + return; + } + VmInstanceVO vm = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vmInstanceUuid) + .find(); + if (vm == null) { + return; + } + if (vm.getHostUuid() != null) { + hostUuids.add(vm.getHostUuid()); + } + if (vm.getLastHostUuid() != null) { + hostUuids.add(vm.getLastHostUuid()); + } + } + + private void handle(APIGetTpmCapabilityMsg msg) { + TpmCapabilityView view = new TpmCapabilityView(); + + final TpmVO tpm = Q.New(TpmVO.class) + .eq(TpmVO_.uuid, msg.getTpmUuid()) + .find(); + final VmInstanceVO vm = Q.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, tpm.getVmInstanceUuid()) + .find(); + view.setTpmInventory(TpmInventory.valueOf(tpm)); + + List files = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vm.getUuid()) + .in(VmHostFileVO_.type, list(VmHostFileType.TpmState, VmHostFileType.NvRam)) + .list(); + view.setFileRefs(VmHostFileInventory.valueOf(files)); + + view.setEdkVersion(VM_EDK.getTokenByResourceUuid(vm.getUuid(), EDK_RPM_TOKEN)); + + if (vm.getHostUuid() != null) { + view.setSwtpmVersion(SWTPM_VERSION.getTokenByResourceUuid(vm.getHostUuid(), SWTPM_VERSION_TOKEN)); + } else if (vm.getLastHostUuid() != null) { + view.setSwtpmVersion(SWTPM_VERSION.getTokenByResourceUuid(vm.getLastHostUuid(), SWTPM_VERSION_TOKEN)); + } + + ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity()); + view.setResetTpmAfterVmCloneConfig(resourceConfig.getResourceConfigValue(vm.getUuid(), Boolean.class)); + + APIGetTpmCapabilityReply reply = new APIGetTpmCapabilityReply(); + reply.setInventory(view); + bus.reply(msg, reply); + } + + private void handle(APIAddTpmMsg msg) { + APIAddTpmEvent event = new APIAddTpmEvent(msg.getId()); + + AddTpmMsg inner = AddTpmMsg.valueOf(msg); + bus.makeTargetServiceIdByResourceUuid(inner, SERVICE_ID, msg.getResourceUuid()); + bus.send(inner, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + event.setInventory(((AddTpmReply) reply.castReply()).getInventory()); + } else { + event.setError(reply.getError()); + } + bus.publish(event); + } + }); + } + + private void handle(APIRemoveTpmMsg msg) { + APIRemoveTpmEvent event = new APIRemoveTpmEvent(msg.getId()); + + RemoveTpmMsg inner = RemoveTpmMsg.valueOf(msg); + bus.makeTargetServiceIdByResourceUuid(inner, SERVICE_ID, msg.getTpmUuid()); + bus.send(inner, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + event.setError(reply.getError()); + } + bus.publish(event); + } + }); + } + + private void handle(APIUpdateTpmMsg msg) { + throw err(NOT_SUPPORTED, "UpdateTpm is not supported in current version").toException(); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/SnapshotGroupRevertTpmHelper.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/SnapshotGroupRevertTpmHelper.java new file mode 100644 index 00000000000..00592c249d6 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/SnapshotGroupRevertTpmHelper.java @@ -0,0 +1,137 @@ +package org.zstack.kvm.tpm; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.compute.vm.VmGlobalConfig; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.core.db.Q; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; +import org.zstack.header.tpm.entity.TpmSpec; +import org.zstack.header.vm.APICreateVmInstanceFromVolumeSnapshotGroupMsg; +import org.zstack.header.vm.CreateVmInstanceMsg; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.devices.NvRamSpec; +import org.zstack.header.vm.devices.VmDevicesSpec; +import org.zstack.kvm.KVMSystemTags; +import org.zstack.resourceconfig.ResourceConfigFacade; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.List; + +import static org.zstack.compute.vm.VmGlobalConfig.ALLOWED_TPM_VM_WITHOUT_KMS; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +public class SnapshotGroupRevertTpmHelper { + private static final CLogger logger = Utils.getLogger(SnapshotGroupRevertTpmHelper.class); + + @Autowired + private ResourceConfigFacade resourceConfigFacade; + @Autowired + private TpmEncryptedResourceKeyBackend tpmKeyBackend; + + public void setupFromApi(APICreateVmInstanceFromVolumeSnapshotGroupMsg apiMsg, CreateVmInstanceMsg cmsg) { + String snapshotGroupUuid = apiMsg.getVolumeSnapshotGroupUuid(); + + boolean resetTpm; + if (apiMsg.getResetTpm() != null) { + resetTpm = apiMsg.getResetTpm(); + } else { + String vmInstanceUuid = Q.New(VolumeSnapshotGroupVO.class) + .select(VolumeSnapshotGroupVO_.vmInstanceUuid) + .eq(VolumeSnapshotGroupVO_.uuid, snapshotGroupUuid) + .findValue(); + resetTpm = resourceConfigFacade.getResourceConfigValue( + VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE, vmInstanceUuid, Boolean.class); + logger.debug(String.format("resetTpm not specified in API, resolved from resource config " + + "RESET_TPM_AFTER_VM_CLONE for vm[uuid:%s]: %s", vmInstanceUuid, resetTpm)); + } + + List backupFiles = Q.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, snapshotGroupUuid) + .list(); + + if (backupFiles.isEmpty()) { + logger.debug(String.format( + "no VmHostBackupFileVO found for volume snapshot group[uuid:%s], skip restoring TPM/NvRam", + snapshotGroupUuid)); + return; + } + + VmHostBackupFileVO tpmBackupFile = null; + VmHostBackupFileVO nvRamBackupFile = null; + for (VmHostBackupFileVO f : backupFiles) { + if (f.getType() == VmHostFileType.TpmState) { + tpmBackupFile = f; + } else if (f.getType() == VmHostFileType.NvRam) { + nvRamBackupFile = f; + } + } + + if (tpmBackupFile == null && nvRamBackupFile == null) { + logger.debug(String.format("no TpmState or NvRam backup file found for volume snapshot group[uuid:%s]", + snapshotGroupUuid)); + return; + } + + VmDevicesSpec devicesSpec = cmsg.getDevicesSpec(); + if (devicesSpec == null) { + devicesSpec = new VmDevicesSpec(); + cmsg.setDevicesSpec(devicesSpec); + } + + if (tpmBackupFile != null) { + TpmSpec tpmSpec = devicesSpec.getTpm(); + if (tpmSpec == null) { + tpmSpec = new TpmSpec(); + devicesSpec.setTpm(tpmSpec); + } + tpmSpec.setEnable(true); + + if (resetTpm) { + // resetTpm=true: reset generate a new one during VM creation + logger.debug(String.format("resetTpm is true for volume snapshot group[uuid:%s], " + + "will reset tpmBackupFileUuid:%s", snapshotGroupUuid, tpmBackupFile.getUuid())); + } else { + tpmSpec.setBackupFileUuid(tpmBackupFile.getUuid()); + } + + if (ALLOWED_TPM_VM_WITHOUT_KMS.value(Boolean.class) != Boolean.TRUE) { + String keyProviderName = KVMSystemTags.TPM_KEY_PROVIDER_NAME + .getTokenByResourceUuid(tpmBackupFile.getUuid(), KVMSystemTags.TPM_KEY_PROVIDER_NAME_TOKEN); + if (keyProviderName == null) { + logger.warn(String.format( + "failed to find keyProvider from snapshotGroup[uuid:%s] by tpmBackupFile[uuid:%s]", + snapshotGroupUuid, tpmBackupFile.getUuid())); + if (tpmSpec.getKeyProviderUuid() == null) { + tpmSpec.setKeyProviderUuid(tpmKeyBackend.defaultKeyProviderUuid()); // maybe null + } + } else { + String keyProviderUuid = tpmKeyBackend.findKeyProviderUuidByName(keyProviderName); + if (keyProviderUuid == null) { + logger.warn(String.format( + "failed to resolve keyProvider[name:%s] from snapshotGroup[uuid:%s] by tpmBackupFile[uuid:%s], keep keyProviderUuid unset", + keyProviderName, snapshotGroupUuid, tpmBackupFile.getUuid())); + } else { + tpmSpec.setKeyProviderUuid(keyProviderUuid); + } + } + } + } + + if (nvRamBackupFile != null) { + NvRamSpec nvRamSpec = devicesSpec.getNvRam(); + if (nvRamSpec == null) { + nvRamSpec = new NvRamSpec(); + devicesSpec.setNvRam(nvRamSpec); + } + nvRamSpec.setBackupFileUuid(nvRamBackupFile.getUuid()); + logger.debug(String.format("set NvRam restore info for volume snapshot group[uuid:%s], nvRamBackupFileUuid:%s", + snapshotGroupUuid, nvRamBackupFile.getUuid())); + } + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostBackupFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostBackupFileBase.java new file mode 100644 index 00000000000..31caa26f219 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostBackupFileBase.java @@ -0,0 +1,82 @@ +package org.zstack.kvm.tpm; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.compute.vm.devices.TpmEncryptedResourceKeyBackend; +import org.zstack.core.db.Q; +import org.zstack.header.tpm.entity.TpmVO; +import org.zstack.header.tpm.entity.TpmVO_; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.kvm.KVMSystemTags; +import org.zstack.kvm.vmfiles.AbstractVmHostBackupFileBase; +import org.zstack.tag.SystemTagCreator; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import static org.zstack.utils.CollectionDSL.e; +import static org.zstack.utils.CollectionDSL.map; + +public class TpmStateVmHostBackupFileBase extends AbstractVmHostBackupFileBase { + private static final CLogger logger = Utils.getLogger(TpmStateVmHostBackupFileBase.class); + + @Autowired + private TpmEncryptedResourceKeyBackend resourceKeyBackend; + + public TpmStateVmHostBackupFileBase(VmHostBackupFileVO self) { + super(self); + } + + @Override + public VmHostFileType type() { + return VmHostFileType.TpmState; + } + + @Override + public void afterBackup(VmHostBackupFileVO from) { + String keyProviderName = KVMSystemTags.TPM_KEY_PROVIDER_NAME + .getTokenByResourceUuid(from.getUuid(), KVMSystemTags.TPM_KEY_PROVIDER_NAME_TOKEN); + if (keyProviderName == null) { + logger.debug(String.format("no tpm key provider name system tag found on source VmHostBackupFileVO[uuid:%s], skip copying", + from.getUuid())); + return; + } + + createKeyProviderNameTag(keyProviderName); + logger.debug(String.format("copied tpm key provider name[%s] from VmHostBackupFileVO[uuid:%s] to VmHostBackupFileVO[uuid:%s]", + keyProviderName, from.getUuid(), self.getUuid())); + } + + @Override + public void afterBackup(VmHostFileVO from) { + String tpmUuid = Q.New(TpmVO.class) + .select(TpmVO_.uuid) + .eq(TpmVO_.vmInstanceUuid, from.getVmInstanceUuid()) + .findValue(); + if (tpmUuid == null) { + logger.debug(String.format("no TpmVO found for vm[uuid:%s], skip creating key provider name tag on VmHostBackupFileVO[uuid:%s]", + from.getVmInstanceUuid(), self.getUuid())); + return; + } + + String keyProviderName = resourceKeyBackend.findKeyProviderNameByTpm(tpmUuid); + if (keyProviderName == null) { + logger.debug(String.format("no key provider name found for tpm[uuid:%s] of vm[uuid:%s], skip creating tag on VmHostBackupFileVO[uuid:%s]", + tpmUuid, from.getVmInstanceUuid(), self.getUuid())); + return; + } + + createKeyProviderNameTag(keyProviderName); + logger.debug(String.format("created tpm key provider name[%s] tag on VmHostBackupFileVO[uuid:%s] from tpm[uuid:%s] of vm[uuid:%s]", + keyProviderName, self.getUuid(), tpmUuid, from.getVmInstanceUuid())); + } + + @SuppressWarnings("unchecked") + private void createKeyProviderNameTag(String keyProviderName) { + SystemTagCreator creator = KVMSystemTags.TPM_KEY_PROVIDER_NAME.newSystemTagCreator(self.getUuid()); + creator.setTagByTokens(map(e(KVMSystemTags.TPM_KEY_PROVIDER_NAME_TOKEN, keyProviderName))); + creator.inherent = true; + creator.recreate = true; + creator.create(); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostFileBase.java new file mode 100644 index 00000000000..32f00975a41 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmStateVmHostFileBase.java @@ -0,0 +1,16 @@ +package org.zstack.kvm.tpm; + +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; +import org.zstack.kvm.vmfiles.AbstractVmHostFileBase; + +public class TpmStateVmHostFileBase extends AbstractVmHostFileBase { + public TpmStateVmHostFileBase(VmHostFileVO self) { + super(self); + } + + @Override + public VmHostFileType type() { + return VmHostFileType.TpmState; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmTO.java new file mode 100644 index 00000000000..073257172f2 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/TpmTO.java @@ -0,0 +1,33 @@ +package org.zstack.kvm.tpm; + +import java.io.Serializable; + +public class TpmTO implements Serializable { + private String keyProviderUuid; + private String secretUuid; + private String installPath; + + public String getKeyProviderUuid() { + return keyProviderUuid; + } + + public void setKeyProviderUuid(String keyProviderUuid) { + this.keyProviderUuid = keyProviderUuid; + } + + public String getSecretUuid() { + return secretUuid; + } + + public void setSecretUuid(String secretUuid) { + this.secretUuid = secretUuid; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java new file mode 100644 index 00000000000..06d7b7c6784 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/VtpmMigratePreAgentContext.java @@ -0,0 +1,102 @@ +package org.zstack.kvm.tpm; + +import org.zstack.header.keyprovider.EncryptedResourceKeyManager.ResourceKeyResult; + +import java.util.Objects; + +/** + * Context for vTPM secret preparation on the source KVM host immediately before the agent + * {@code migrate-vm} call. + *

+ * Built during {@link org.zstack.header.vm.VmInstanceMigrateExtensionPoint#preMigrateVm} (e.g. in + * {@link KvmTpmExtensions}) to hold VM/source/destination hosts and resolved TPM/key/secret fields. + * This replaces the previous split between a thin migrate wrapper + * and a separate object meant for FlowChain {@code Map} storage. + */ +public final class VtpmMigratePreAgentContext { + private final String vmUuid; + private final String srcHostUuid; + private final String dstHostUuid; + private boolean enableKeyProvider = true; + + private String tpmUuid; + private String providerUuid; + private String providerName; + private Integer keyVersion; + private ResourceKeyResult resourceKeyResult; + private String sourceSecretUuid; + + public VtpmMigratePreAgentContext(String vmUuid, String srcHostUuid, String dstHostUuid) { + this.vmUuid = Objects.requireNonNull(vmUuid, "vmUuid"); + this.srcHostUuid = Objects.requireNonNull(srcHostUuid, "srcHostUuid"); + this.dstHostUuid = Objects.requireNonNull(dstHostUuid, "dstHostUuid"); + } + + public String getVmUuid() { + return vmUuid; + } + + public String getSrcHostUuid() { + return srcHostUuid; + } + + public String getDstHostUuid() { + return dstHostUuid; + } + + public boolean isEnableKeyProvider() { + return enableKeyProvider; + } + + public void setEnableKeyProvider(boolean enableKeyProvider) { + this.enableKeyProvider = enableKeyProvider; + } + + public String getTpmUuid() { + return tpmUuid; + } + + public void setTpmUuid(String tpmUuid) { + this.tpmUuid = tpmUuid; + } + + public String getProviderUuid() { + return providerUuid; + } + + public void setProviderUuid(String providerUuid) { + this.providerUuid = providerUuid; + } + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + public Integer getKeyVersion() { + return keyVersion; + } + + public void setKeyVersion(Integer keyVersion) { + this.keyVersion = keyVersion; + } + + public ResourceKeyResult getResourceKeyResult() { + return resourceKeyResult; + } + + public void setResourceKeyResult(ResourceKeyResult resourceKeyResult) { + this.resourceKeyResult = resourceKeyResult; + } + + public String getSourceSecretUuid() { + return sourceSecretUuid; + } + + public void setSourceSecretUuid(String sourceSecretUuid) { + this.sourceSecretUuid = sourceSecretUuid; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmMsg.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmMsg.java new file mode 100644 index 00000000000..60722841ed2 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmMsg.java @@ -0,0 +1,35 @@ +package org.zstack.kvm.tpm.message; + +import org.zstack.header.message.NeedReplyMessage; + +import java.util.List; + +public class CloneVmTpmMsg extends NeedReplyMessage { + private String srcVmUuid; + private List dstVmUuidList; + private Boolean resetTpm; + + public String getSrcVmUuid() { + return srcVmUuid; + } + + public void setSrcVmUuid(String srcVmUuid) { + this.srcVmUuid = srcVmUuid; + } + + public List getDstVmUuidList() { + return dstVmUuidList; + } + + public void setDstVmUuidList(List dstVmUuidList) { + this.dstVmUuidList = dstVmUuidList; + } + + public Boolean getResetTpm() { + return resetTpm; + } + + public void setResetTpm(Boolean resetTpm) { + this.resetTpm = resetTpm; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmReply.java b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmReply.java new file mode 100644 index 00000000000..0c9be5fb4f3 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/tpm/message/CloneVmTpmReply.java @@ -0,0 +1,18 @@ +package org.zstack.kvm.tpm.message; + +import org.zstack.header.message.MessageReply; +import org.zstack.header.tpm.entity.TpmInventory; + +import java.util.List; + +public class CloneVmTpmReply extends MessageReply { + private List inventories; + + public List getInventories() { + return inventories; + } + + public void setInventories(List inventories) { + this.inventories = inventories; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostBackupFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostBackupFileBase.java new file mode 100644 index 00000000000..1e4e7bae71b --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostBackupFileBase.java @@ -0,0 +1,26 @@ +package org.zstack.kvm.vmfiles; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; + +import java.util.Objects; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +public abstract class AbstractVmHostBackupFileBase { + protected VmHostBackupFileVO self; + + protected AbstractVmHostBackupFileBase(VmHostBackupFileVO self) { + this.self = Objects.requireNonNull(self); + } + + public abstract VmHostFileType type(); + + public void afterBackup(VmHostBackupFileVO from) { + } + + public void afterBackup(VmHostFileVO from) { + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostFileBase.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostFileBase.java new file mode 100644 index 00000000000..8e87dcf70cd --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/AbstractVmHostFileBase.java @@ -0,0 +1,19 @@ +package org.zstack.kvm.vmfiles; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.header.vm.additions.VmHostFileType; +import org.zstack.header.vm.additions.VmHostFileVO; + +import java.util.Objects; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +public abstract class AbstractVmHostFileBase { + protected VmHostFileVO self; + + protected AbstractVmHostFileBase(VmHostFileVO self) { + this.self = Objects.requireNonNull(self); + } + + public abstract VmHostFileType type(); +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java new file mode 100644 index 00000000000..c68369842fc --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/VmHostFileTracker.java @@ -0,0 +1,575 @@ +package org.zstack.kvm.vmfiles; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.CoreGlobalProperty; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.cloudbus.CloudBus; +import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.cloudbus.EventCallback; +import org.zstack.core.cloudbus.EventFacade; +import org.zstack.core.cloudbus.ResourceDestinationMaker; +import org.zstack.core.config.GlobalConfigUpdateExtensionPoint; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +import org.zstack.core.db.SQLBatch; +import org.zstack.core.thread.PeriodicTask; +import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.timeout.TimeHelper; +import org.zstack.header.Component; +import org.zstack.header.core.FutureReturnValueCompletion; +import org.zstack.header.core.WhileDoneCompletion; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.host.HostVO; +import org.zstack.header.host.HostVO_; +import org.zstack.header.message.MessageReply; +import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO; +import org.zstack.header.vm.VmCanonicalEvents; +import org.zstack.header.vm.VmInstanceAO_; +import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceEO; +import org.zstack.header.vm.VmInstanceState; +import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.additions.*; +import org.zstack.header.vo.ResourceVO; +import org.zstack.header.vo.ResourceVO_; +import org.zstack.kvm.KVMAgentCommands; +import org.zstack.kvm.KVMConstant; +import org.zstack.kvm.KVMGlobalConfig; +import org.zstack.kvm.KvmCommandSender; +import org.zstack.kvm.vmfiles.message.SyncVmHostFilesFromHostMsg; +import org.zstack.utils.CollectionUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.data.Pair; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.Tuple; +import java.sql.Timestamp; +import java.util.*; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.zstack.core.Platform.operr; +import static org.zstack.utils.CollectionUtils.toMap; +import static org.zstack.utils.CollectionUtils.transformToSet; + +public class VmHostFileTracker implements Component { + private static final CLogger logger = Utils.getLogger(VmHostFileTracker.class); + + private static final long CLEANUP_INTERVAL_SECONDS = 1800; + private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); + private static final long SEVEN_DAYS_MS = TimeUnit.DAYS.toMillis(7); + private static final long NINETY_DAYS_MS = TimeUnit.DAYS.toMillis(90); + private static final long KVM_CMD_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(3); + + @Autowired + private ThreadFacade threadFacade; + @Autowired + private CloudBus bus; + @Autowired + private ResourceDestinationMaker destinationMaker; + @Autowired + private DatabaseFacade dbf; + @Autowired + private TimeHelper timeHelper; + @Autowired + private EventFacade eventFacade; + + private Future trackerThread; + private Future cleanupThread; + + @Override + public boolean start() { + if (CoreGlobalProperty.UNIT_TEST_ON) { + logger.info("VmHostFileTracker is disabled in unit test"); + return true; + } + + submitTrackerTask(); + submitCleanupTask(); + setupCanonicalEvents(); + + GlobalConfigUpdateExtensionPoint listener = (oldConfig, newConfig) -> { + logger.debug(String.format("%s changed from %s to %s, restarting vm-host-file-tracker", + newConfig.getCanonicalName(), oldConfig.value(), newConfig.value())); + submitTrackerTask(); + }; + + KVMGlobalConfig.VM_HOST_FILE_SYNC_INTERVAL.installUpdateExtension(listener); + KVMGlobalConfig.VM_HOST_FILE_SYNC_CONCURRENCY.installUpdateExtension(listener); + + return true; + } + + @Override + public boolean stop() { + if (trackerThread != null) { + trackerThread.cancel(true); + } + if (cleanupThread != null) { + cleanupThread.cancel(true); + } + return true; + } + + private void setupCanonicalEvents() { + eventFacade.on(VmCanonicalEvents.VM_HOST_FILE_CHANGED_PATH, new EventCallback() { + @Override + protected void run(Map tokens, Object data) { + if (!(data instanceof VmCanonicalEvents.VmHostFileChangedData)) { + return; + } + + VmCanonicalEvents.VmHostFileChangedData d = (VmCanonicalEvents.VmHostFileChangedData) data; + String hostUuid = d.getHostUuid(); + String vmUuid = d.getVmUuid(); + List types = d.getTypes(); + + if (hostUuid == null || vmUuid == null || types == null || types.isEmpty()) { + logger.warn(String.format("received VM_HOST_FILE_CHANGED event with incomplete data: hostUuid=%s, vmUuid=%s", hostUuid, vmUuid)); + return; + } + + Timestamp now = new Timestamp(timeHelper.getCurrentTimeMillis()); + for (String type : types) { + VmHostFileType fileType; + try { + fileType = VmHostFileType.valueOf(type); + } catch (Exception e) { + logger.warn(String.format("ignore invalid vm host file type in event: vmUuid=%s, hostUuid=%s, type=%s", + vmUuid, hostUuid, type), e); + continue; + } + + long updated = SQL.New(VmHostFileVO.class) + .eq(VmHostFileVO_.hostUuid, hostUuid) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.type, fileType) + .set(VmHostFileVO_.changeDate, now) + .update(); + + if (updated > 0) { + logger.debug(String.format("marked VmHostFile changed: vmUuid=%s, hostUuid=%s, type=%s", vmUuid, hostUuid, type)); + } else { + logger.warn(String.format("no VmHostFileVO found for vmUuid=%s, hostUuid=%s, type=%s", vmUuid, hostUuid, type)); + } + } + } + }); + } + + private synchronized void submitTrackerTask() { + if (trackerThread != null) { + trackerThread.cancel(true); + } + + long interval = KVMGlobalConfig.VM_HOST_FILE_SYNC_INTERVAL.value(Long.class); + trackerThread = threadFacade.submitPeriodicTask(new PeriodicTask() { + @Override + public TimeUnit getTimeUnit() { + return TimeUnit.SECONDS; + } + + @Override + public long getInterval() { + return interval; + } + + @Override + public String getName() { + return "vm-host-file-tracker"; + } + + @Override + public void run() { + logger.info("VmHostFileTracker: starting periodic check of VM host files"); + syncVmHostFiles(); + } + }); + } + + private synchronized void submitCleanupTask() { + if (cleanupThread != null) { + cleanupThread.cancel(true); + } + + cleanupThread = threadFacade.submitPeriodicTask(new PeriodicTask() { + @Override + public TimeUnit getTimeUnit() { + return TimeUnit.SECONDS; + } + + @Override + public long getInterval() { + return CLEANUP_INTERVAL_SECONDS; + } + + @Override + public String getName() { + return "vm-host-file-cleanup-tracker"; + } + + @Override + public void run() { + logger.info("VmHostFileTracker: starting periodic cleanup of expired VM host files"); + try { + cleanupExpiredVmHostFiles(); + } catch (Throwable t) { + logger.warn("error during VmHostFile cleanup", t); + } + try { + cleanupExpiredVmHostBackupFiles(); + } catch (Throwable t) { + logger.warn("error during VmHostBackupFile cleanup", t); + } + } + }); + } + + private void syncVmHostFiles() { + List hostFiles = Q.New(VmHostFileVO.class, VmInstanceVO.class) + .table0() + .eq(VmHostFileVO_.vmInstanceUuid).table1(VmInstanceAO_.uuid) + .eq(VmHostFileVO_.hostUuid).table1(VmInstanceAO_.hostUuid) + .selectThisTable() + .table1() + .eq(VmInstanceAO_.state, VmInstanceState.Running) + .list(); + + if (hostFiles.isEmpty()) { + return; + } + + Map> grouped = hostFiles.stream() + .collect(Collectors.groupingBy(f -> f.getVmInstanceUuid() + "::" + f.getHostUuid())); + List> groups = new ArrayList<>(grouped.values()); + + long now = timeHelper.getCurrentTimeMillis(); + long checkIntervalMs = KVMGlobalConfig.VM_HOST_FILE_SYNC_INTERVAL.value(Long.class) * 1000; + long forceSyncThresholdMs = checkIntervalMs * 100; + int concurrency = KVMGlobalConfig.VM_HOST_FILE_SYNC_CONCURRENCY.value(Integer.class); + + new While<>(groups).step((group, whileCompletion) -> { + VmHostFileVO first = group.get(0); + String vmUuid = first.getVmInstanceUuid(); + String hostUuid = first.getHostUuid(); + + if (!destinationMaker.isManagedByUs(vmUuid)) { + whileCompletion.done(); + return; + } + + boolean hasChanged = group.stream().anyMatch(f -> f.getChangeDate() != null); + String syncReason; + if (hasChanged) { + syncReason = VmHostFileSyncReason.PeriodicDirtyCheck.reason(); + } else { + // check if force sync is needed based on lastSyncDate + Timestamp oldestLastSync = group.stream() + .map(VmHostFileVO::getLastSyncDate) + .filter(Objects::nonNull) + .min(Comparator.naturalOrder()) + .orElse(null); + + if (oldestLastSync != null && (now - oldestLastSync.getTime()) < forceSyncThresholdMs) { + whileCompletion.done(); + return; + } + syncReason = VmHostFileSyncReason.PeriodicForceSync.reason(); + } + + SyncVmHostFilesFromHostMsg syncMsg = new SyncVmHostFilesFromHostMsg(); + syncMsg.setHostUuid(hostUuid); + syncMsg.setVmUuid(vmUuid); + syncMsg.setSyncReason(syncReason); + + for (VmHostFileVO file : group) { + if (file.getType() == VmHostFileType.NvRam) { + syncMsg.setNvRamPath(file.getPath()); + } else if (file.getType() == VmHostFileType.TpmState) { + syncMsg.setTpmStateFolder(file.getPath()); + } + } + + bus.makeLocalServiceId(syncMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(syncMsg, new CloudBusCallBack(whileCompletion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + logger.warn(String.format("failed to sync VM host file[vmUuid=%s] from host[uuid=%s]: %s", + vmUuid, hostUuid, reply.getError().getReadableDetails())); + } + whileCompletion.done(); + } + }); + }, concurrency).run(new WhileDoneCompletion(null) { + @Override + public void done(ErrorCodeList errorCodeList) { + // periodic check round finished, empty callback + } + }); + } + + /** + * active VM: In each type, max(lastOpDate) will not be deleted; + * delete lastOpDate > 1d if delete command success + * VmInstanceEO(deleted): delete lastOpDate > 90d if record is latest; + * delete lastOpDate > 7d if record is not latest even if delete command failed + * delete lastOpDate < 7d if record is not latest and delete command success + * VmInstanceEO(not exists): delete + * Host not exists: delete and not send command + */ + private void cleanupExpiredVmHostFiles() { + List vmTypeList = Q.New(VmHostFileVO.class) + .select(VmHostFileVO_.vmInstanceUuid, VmHostFileVO_.type) + .listTuple(); + if (vmTypeList.isEmpty()) { + return; + } + + Set> pairs = new HashSet<>(); + for (Tuple tuple : vmTypeList) { + pairs.add(new Pair<>(tuple.get(0, String.class), tuple.get(1, VmHostFileType.class))); + } + + long now = timeHelper.getCurrentTimeMillis(); + CollectionUtils.safeForEach(pairs, pair -> { + String vmUuid = pair.first(); + if (!destinationMaker.isManagedByUs(vmUuid)) { + return; + } + + VmHostFileType type = pair.second(); + List files = Q.New(VmHostFileVO.class) + .eq(VmHostFileVO_.vmInstanceUuid, vmUuid) + .eq(VmHostFileVO_.type, type) + .list(); + if (files.isEmpty()) { + return; // should not be here + } + files.sort((a, b) -> b.getLastOpDate().compareTo(a.getLastOpDate())); + + // Re-check VM status each iteration as per user's requirement + VmInstanceEO eo = Q.New(VmInstanceEO.class).eq(VmInstanceVO_.uuid, vmUuid).find(); + boolean vmActive = eo != null && eo.getDeleted() == null; + + if (vmActive) { + cleanupActiveVmHostFiles(files, now); + } else if (eo != null && eo.getDeleted() != null) { + cleanupDeletedVmHostFiles(files, now); + } else { + for (VmHostFileVO file : files) { + logger.info(String.format("deleting VmHostFileVO[uuid=%s] for expunged VM[uuid=%s]", + file.getUuid(), vmUuid)); + deleteVmHostFileFromDb(file.getUuid()); + } + } + }); + } + + private void cleanupActiveVmHostFiles(List files, long now) { + if (files.size() <= 1) { + return; + } + + VmHostFileVO latestWithContent = null; + for (VmHostFileVO file : files) { + if (hasContent(file.getUuid())) { + latestWithContent = file; + break; + } + } + + boolean allOlderThanOneDay = files.stream() + .allMatch(f -> now - f.getLastOpDate().getTime() > ONE_DAY_MS); + + for (int i = 1; i < files.size(); i++) { + VmHostFileVO file = files.get(i); + long age = now - file.getLastOpDate().getTime(); + + if (age <= ONE_DAY_MS) { + continue; + } + + if (allOlderThanOneDay && latestWithContent != null + && latestWithContent.getUuid().equals(file.getUuid()) + && !latestWithContent.getUuid().equals(files.get(0).getUuid())) { + continue; + } + + if (hasContent(file.getUuid())) { + boolean latestHasContent = hasContent(files.get(0).getUuid()); + if (!latestHasContent && latestWithContent != null + && latestWithContent.getUuid().equals(file.getUuid())) { + continue; + } + } + + boolean cmdSuccess = sendDeleteCommandToHost(file); + if (cmdSuccess) { + logger.info(String.format("deleting expired VmHostFileVO[uuid=%s, vm=%s, type=%s, age=%d hours]", + file.getUuid(), file.getVmInstanceUuid(), file.getType(), TimeUnit.MILLISECONDS.toHours(age))); + deleteVmHostFileFromDb(file.getUuid()); + } else { + logger.warn(String.format("failed to delete host file for VmHostFileVO[uuid=%s], skipping DB deletion", + file.getUuid())); + } + } + } + + private void cleanupDeletedVmHostFiles(List files, long now) { + for (int i = 0; i < files.size(); i++) { + VmHostFileVO file = files.get(i); + long age = now - file.getLastOpDate().getTime(); + + if (i == 0 && age <= NINETY_DAYS_MS) { + continue; + } + + if (age <= SEVEN_DAYS_MS) { + boolean cmdSuccess = sendDeleteCommandToHost(file); + if (!cmdSuccess) { + logger.warn(String.format( + "KVM delete failed for VmHostFileVO[uuid=%s] of deleted VM, lastOpDate within 7 days, skipping", + file.getUuid())); + continue; + } + } else { + sendDeleteCommandToHost(file); + } + + logger.info(String.format("deleting VmHostFileVO[uuid=%s] for deleted VM[uuid=%s, type=%s, age=%d days]", + file.getUuid(), file.getVmInstanceUuid(), file.getType(), TimeUnit.MILLISECONDS.toDays(age))); + deleteVmHostFileFromDb(file.getUuid()); + } + } + + /** + * lastOpDate <= 7d: do not delete + * resourceVO no longer exists: delete + * -> VolumeSnapshotGroupVO: do not delete + * -> VmInstanceEO (deleted): delete if VM has deleted >= 90d + * -> Other: do not delete + */ + private void cleanupExpiredVmHostBackupFiles() { + long now = timeHelper.getCurrentTimeMillis(); + Timestamp daysBefore = new Timestamp(now - SEVEN_DAYS_MS); + + List allBackupFiles = Q.New(VmHostBackupFileVO.class) + .lt(VmHostBackupFileVO_.lastOpDate, daysBefore) + .list(); + if (allBackupFiles.isEmpty()) { + return; + } + + List resourceTypeTuples = Q.New(ResourceVO.class) + .in(ResourceVO_.uuid, transformToSet(allBackupFiles, VmHostBackupFileVO::getResourceUuid)) + .select(ResourceVO_.uuid, ResourceVO_.resourceType) + .listTuple(); + Map uuidTypeMap = toMap(resourceTypeTuples, + tuple -> tuple.get(0, String.class), + tuple -> tuple.get(1, String.class)); + + CollectionUtils.safeForEach(allBackupFiles, backupFile -> { + String resourceUuid = backupFile.getResourceUuid(); + if (!destinationMaker.isManagedByUs(resourceUuid)) { + return; + } + + String resourceType = uuidTypeMap.get(resourceUuid); + if (resourceType == null) { + logger.info(String.format( + "deleting VmHostBackupFileVO[uuid=%s] -> referenced resource[uuid=%s] no longer exists", + backupFile.getUuid(), resourceUuid)); + deleteVmHostBackupFileFromDb(backupFile.getUuid()); + return; + } + + if (VolumeSnapshotGroupVO.class.getSimpleName().equals(resourceType)) { + return; + } + + if (VmInstanceVO.class.getSimpleName().equals(resourceType)) { + VmInstanceEO eo = dbf.findByUuid(resourceUuid, VmInstanceEO.class); + if (eo != null && eo.getDeleted() != null) { + long deletedAge; + try { + Timestamp deletedTime = Timestamp.valueOf(eo.getDeleted()); + deletedAge = now - deletedTime.getTime(); + } catch (IllegalArgumentException e) { + deletedAge = now - eo.getLastOpDate().getTime(); + } + + try { + if (deletedAge > NINETY_DAYS_MS) { + logger.info(String.format( + "deleting VmHostBackupFileVO[uuid=%s] -> referenced VM[uuid=%s] was deleted %d days ago", + backupFile.getUuid(), resourceUuid, TimeUnit.MILLISECONDS.toDays(deletedAge))); + deleteVmHostBackupFileFromDb(backupFile.getUuid()); + } + } catch (Exception e) { + logger.warn(String.format("failed to parse deleted timestamp[%s] for VM[uuid=%s]", + eo.getDeleted(), resourceUuid), e); + } + } + } + }); + } + + /** + * Send a KVM delete command to the host for the given file. + * Returns true if the command succeeded or the host doesn't exist (allowing DB cleanup). + */ + private boolean sendDeleteCommandToHost(VmHostFileVO file) { + String hostUuid = file.getHostUuid(); + + // If host is gone (soft-deleted or expunged), allow direct DB cleanup + if (!Q.New(HostVO.class).eq(HostVO_.uuid, hostUuid).isExists()) { + return true; + } + + KVMAgentCommands.WriteVmHostFileContentCmd cmd = new KVMAgentCommands.WriteVmHostFileContentCmd(); + KVMAgentCommands.VmHostFileTO to = new KVMAgentCommands.VmHostFileTO(); + to.setPath(file.getPath()); + to.setType(file.getType().toString()); + to.setOperation(VmHostFileOperation.Delete.toString()); + cmd.setHostFiles(Collections.singletonList(to)); + + FutureReturnValueCompletion future = new FutureReturnValueCompletion(null); + new KvmCommandSender(hostUuid).send(cmd, KVMConstant.WRITE_VM_HOST_FILE_PATH, wrapper -> { + KVMAgentCommands.WriteVmHostFileContentResponse rsp = + wrapper.getResponse(KVMAgentCommands.WriteVmHostFileContentResponse.class); + return rsp.isSuccess() ? null : operr("failed to delete host file[path=%s] on host[uuid=%s]", + file.getPath(), hostUuid); + }, future); + + future.await(KVM_CMD_TIMEOUT_MS); + return future.isSuccess(); + } + + private boolean hasContent(String fileUuid) { + return Q.New(VmHostFileContentVO.class).eq(VmHostFileContentVO_.uuid, fileUuid).isExists(); + } + + private void deleteVmHostFileFromDb(String fileUuid) { + new SQLBatch() { + @Override + protected void scripts() { + sql(VmHostFileContentVO.class).eq(VmHostFileContentVO_.uuid, fileUuid).delete(); + sql(VmHostFileVO.class).eq(VmHostFileVO_.uuid, fileUuid).delete(); + } + }.execute(); + } + + private void deleteVmHostBackupFileFromDb(String fileUuid) { + new SQLBatch() { + @Override + protected void scripts() { + sql(VmHostFileContentVO.class).eq(VmHostFileContentVO_.uuid, fileUuid).delete(); + sql(VmHostBackupFileVO.class).eq(VmHostBackupFileVO_.uuid, fileUuid).delete(); + } + }.execute(); + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileMsg.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileMsg.java new file mode 100644 index 00000000000..18a0930d7fc --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileMsg.java @@ -0,0 +1,35 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.NeedReplyMessage; + +import java.util.List; + +public class BackupVmHostFileMsg extends NeedReplyMessage { + private String vmUuid; + private String hostUuid; + private List toResourceUuidList; + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public List getToResourceUuidList() { + return toResourceUuidList; + } + + public void setToResourceUuidList(List toResourceUuidList) { + this.toResourceUuidList = toResourceUuidList; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorMsg.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorMsg.java new file mode 100644 index 00000000000..1ac815f8ff9 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorMsg.java @@ -0,0 +1,29 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.host.HostMessage; +import org.zstack.header.message.NeedReplyMessage; +import org.zstack.header.vm.additions.VmHostFileBackupJob; + +import java.util.List; + +public class BackupVmHostFileOnHypervisorMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + private List vmHostFileBackupJobs; + + @Override + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public List getVmHostFileBackupJobs() { + return vmHostFileBackupJobs; + } + + public void setVmHostFileBackupJobs(List vmHostFileBackupJobs) { + this.vmHostFileBackupJobs = vmHostFileBackupJobs; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorReply.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorReply.java new file mode 100644 index 00000000000..f3268a43f83 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileOnHypervisorReply.java @@ -0,0 +1,6 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.MessageReply; + +public class BackupVmHostFileOnHypervisorReply extends MessageReply { +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileReply.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileReply.java new file mode 100644 index 00000000000..f2a22b3d920 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/BackupVmHostFileReply.java @@ -0,0 +1,17 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.MessageReply; + +import java.util.List; + +public class BackupVmHostFileReply extends MessageReply { + private List backupFileUuidList; + + public List getBackupFileUuidList() { + return backupFileUuidList; + } + + public void setBackupFileUuidList(List backupFileUuidList) { + this.backupFileUuidList = backupFileUuidList; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileMsg.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileMsg.java new file mode 100644 index 00000000000..ece11bc11fc --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileMsg.java @@ -0,0 +1,35 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.NeedReplyMessage; + +import java.util.List; + +public class CloneVmHostFileMsg extends NeedReplyMessage { + private String srcVmUuid; + private List dstVmUuidList; + private Boolean resetTpm; + + public String getSrcVmUuid() { + return srcVmUuid; + } + + public void setSrcVmUuid(String srcVmUuid) { + this.srcVmUuid = srcVmUuid; + } + + public List getDstVmUuidList() { + return dstVmUuidList; + } + + public void setDstVmUuidList(List dstVmUuidList) { + this.dstVmUuidList = dstVmUuidList; + } + + public Boolean getResetTpm() { + return resetTpm; + } + + public void setResetTpm(Boolean resetTpm) { + this.resetTpm = resetTpm; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileReply.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileReply.java new file mode 100644 index 00000000000..b29e604ed39 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/CloneVmHostFileReply.java @@ -0,0 +1,6 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.MessageReply; + +public class CloneVmHostFileReply extends MessageReply { +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostMsg.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostMsg.java new file mode 100644 index 00000000000..93b21797f14 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostMsg.java @@ -0,0 +1,69 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.NeedReplyMessage; + +public class SyncVmHostFilesFromHostMsg extends NeedReplyMessage { + private String hostUuid; + private String vmUuid; + private String nvRamPath; + private String tpmStateFolder; + private String syncReason; + private boolean syncToBackup; + private String backupResourceUuid; + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getVmUuid() { + return vmUuid; + } + + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; + } + + public String getNvRamPath() { + return nvRamPath; + } + + public void setNvRamPath(String nvRamPath) { + this.nvRamPath = nvRamPath; + } + + public String getTpmStateFolder() { + return tpmStateFolder; + } + + public void setTpmStateFolder(String tpmStateFolder) { + this.tpmStateFolder = tpmStateFolder; + } + + public String getSyncReason() { + return syncReason; + } + + public void setSyncReason(String syncReason) { + this.syncReason = syncReason; + } + + public boolean isSyncToBackup() { + return syncToBackup; + } + + public void setSyncToBackup(boolean syncToBackup) { + this.syncToBackup = syncToBackup; + } + + public String getBackupResourceUuid() { + return backupResourceUuid; + } + + public void setBackupResourceUuid(String backupResourceUuid) { + this.backupResourceUuid = backupResourceUuid; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostReply.java b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostReply.java new file mode 100644 index 00000000000..e920c800985 --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/vmfiles/message/SyncVmHostFilesFromHostReply.java @@ -0,0 +1,6 @@ +package org.zstack.kvm.vmfiles.message; + +import org.zstack.header.message.MessageReply; + +public class SyncVmHostFilesFromHostReply extends MessageReply { +} 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..a586da2bf48 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 = "VolumeUuidToVmUuidResolver", field = "volumeUuid", 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..1642c1c1097 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 @@ -36,9 +36,13 @@ import org.zstack.header.message.MessageReply; import org.zstack.header.message.OverlayMessage; import org.zstack.header.storage.primary.*; +import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageMsg; +import org.zstack.header.storage.primary.CleanupVmInstanceMetadataOnPrimaryStorageReply; 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.*; @@ -65,7 +69,8 @@ import static org.zstack.core.Platform.*; import static org.zstack.storage.primary.local.LocalStorageUtils.getHostUuidFromInstallUrl; import static org.zstack.utils.CollectionDSL.*; -import static org.zstack.utils.CollectionUtils.*; +import static org.zstack.utils.CollectionUtils.toMap; +import static org.zstack.utils.CollectionUtils.transformAndRemoveNull; /** * Created by frank on 6/30/2015. @@ -1640,6 +1645,33 @@ public void fail(ErrorCode errorCode) { }); } + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + final LocalStorageHypervisorBackend bkd; + try { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(msg.getHostUuid()); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + reply.setError(operr("failed to resolve host/backend for rebase on local primary storage[uuid:%s], hostUuid[%s]: %s", + self.getUuid(), msg.getHostUuid(), e.getMessage())); + bus.reply(msg, reply); + return; + } + 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 +3361,266 @@ 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 String.format("update-metadata-on-ps-%s", self.getUuid()); + } + + @Override + public int getSyncLevel() { + return 10; + } + + @Override + public void run(SyncTaskChain chain) { + final String hostUuid; + final LocalStorageHypervisorBackend bkd; + try { + hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("failed to resolve host for vm metadata update on local primary storage[uuid:%s], rootVolumeUuid[%s]: %s", + self.getUuid(), msg.getRootVolumeUuid(), e.getMessage())); + bus.reply(msg, reply); + chain.next(); + return; + } + 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 String.format("update-metadata-on-ps-%s", self.getUuid()); + } + }); + } + + @Override + protected void handle(final GetVmInstanceMetadataFromPrimaryStorageMsg msg) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + + String hostUuid = null; + if (msg.getHostUuid() != null) { + hostUuid = msg.getHostUuid(); + } + + if (hostUuid == null) { + if (msg.getRootVolumeUuid() == null) { + reply.setError(operr("cannot determine host for vm metadata get on local primary storage[uuid:%s]," + + " rootVolumeUuid is null", self.getUuid())); + bus.reply(msg, reply); + return; + } + try { + hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); + } catch (Exception e) { + reply.setError(operr("cannot determine host for vm metadata get on local primary storage[uuid:%s], rootVolumeUuid[%s]: %s", + self.getUuid(), msg.getRootVolumeUuid(), e.getMessage())); + bus.reply(msg, reply); + return; + } + } + + final LocalStorageHypervisorBackend bkd; + try { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + reply.setError(operr("failed to resolve host/backend for vm metadata get on local primary storage[uuid:%s], hostUuid[%s]: %s", + self.getUuid(), hostUuid, e.getMessage())); + bus.reply(msg, reply); + return; + } + 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 allHostUuids = SQL.New( + "select h.hostUuid from LocalStorageHostRefVO h, HostVO host" + + " where h.primaryStorageUuid = :psUuid" + + " and h.hostUuid = host.uuid", String.class) + .param("psUuid", self.getUuid()) + .list(); + if (allHostUuids.isEmpty()) { + reply.setError(operr("no host found for local primary storage[uuid:%s]", self.getUuid())); + bus.reply(msg, reply); + return; + } + + 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], " + + "total hosts=%d", self.getUuid(), allHostUuids.size())); + bus.reply(msg, reply); + return; + } + + // Disconnected hosts cannot be scanned; treat them as failed upfront + List disconnectedHostUuids = new ArrayList<>(allHostUuids); + disconnectedHostUuids.removeAll(connectedHostUuids); + + List allSummaries = Collections.synchronizedList(new ArrayList<>()); + List failedHosts = Collections.synchronizedList(new ArrayList<>(disconnectedHostUuids)); + if (!disconnectedHostUuids.isEmpty()) { + logger.warn(String.format("local primary storage[uuid:%s] has %d disconnected hosts that cannot be scanned: %s", + self.getUuid(), disconnectedHostUuids.size(), disconnectedHostUuids)); + } + + new While<>(connectedHostUuids).all((hostUuid, com) -> { + final LocalStorageHypervisorBackend bkd; + try { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + failedHosts.add(hostUuid); + com.addError(operr("failed to prepare vm metadata scan on host[uuid:%s]: %s", + hostUuid, e.getMessage())); + com.done(); + return; + } + 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)); + failedHosts.add(hostUuid); + 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)); + if (!failedHosts.isEmpty()) { + logger.warn(String.format("partial scan failure on local primary storage[uuid:%s], " + + "failedHosts=%s, successHosts=%d/%d", self.getUuid(), failedHosts, + connectedHostUuids.size() - failedHosts.size(), connectedHostUuids.size())); + } + } + bus.reply(msg, reply); + } + }); + } + + @Override + protected void handle(final CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("update-metadata-on-ps-%s-%s", self.getUuid(), msg.getVmInstanceUuid()); + } + + @Override + public int getSyncLevel() { + return 1; + } + + @Override + public void run(SyncTaskChain chain) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + + String hostUuid = msg.getHostUuid(); + if (hostUuid == null && msg.getRootVolumeUuid() != null) { + try { + hostUuid = getHostUuidByResourceUuid(msg.getRootVolumeUuid()); + } catch (Exception e) { + logger.warn(String.format("failed to get host by rootVolumeUuid[%s]: %s", msg.getRootVolumeUuid(), e.getMessage())); + } + } + + if (hostUuid == null) { + reply.setError(operr("cannot determine host for vm metadata cleanup on local primary storage[uuid:%s]," + + " rootVolumeUuid[%s], hostUuid is null", self.getUuid(), msg.getRootVolumeUuid())); + bus.reply(msg, reply); + chain.next(); + return; + } + + final LocalStorageHypervisorBackend bkd; + try { + LocalStorageHypervisorFactory f = getHypervisorBackendFactoryByHostUuid(hostUuid); + bkd = f.getHypervisorBackend(self); + } catch (Exception e) { + reply.setError(operr("failed to resolve host/backend for vm metadata cleanup on local primary storage[uuid:%s], hostUuid[%s]: %s", + self.getUuid(), hostUuid, e.getMessage())); + bus.reply(msg, reply); + chain.next(); + return; + } + bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg, chain) { + @Override + public void success(CleanupVmInstanceMetadataOnPrimaryStorageReply returnValue) { + bus.reply(msg, returnValue); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + chain.next(); + } + }); + } + + @Override + public String getName() { + return String.format("cleanup-metadata-on-ps-%s-%s", self.getUuid(), msg.getVmInstanceUuid()); + } + }); + } } 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..d8226d932b2 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java @@ -7,6 +7,8 @@ import org.zstack.header.image.ImageInventory; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.snapshot.VolumeSnapshotInventory; +import org.zstack.header.vm.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 +123,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..93b88a397c9 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 @@ -33,6 +33,7 @@ import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.*; import org.zstack.header.image.ImageBackupStorageRefInventory; +import org.zstack.header.image.ImageConstant; import org.zstack.header.image.ImageConstant.ImageMediaType; import org.zstack.header.image.ImageInventory; import org.zstack.header.image.ImageStatus; @@ -42,11 +43,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 +72,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; @@ -202,6 +205,7 @@ public static class CreateEmptyVolumeCmd extends AgentCommand { private String name; private String volumeUuid; private String backingFile; + private String volumeFormat; public String getBackingFile() { return backingFile; @@ -211,6 +215,14 @@ public void setBackingFile(String backingFile) { this.backingFile = backingFile; } + public String getVolumeFormat() { + return volumeFormat; + } + + public void setVolumeFormat(String volumeFormat) { + this.volumeFormat = volumeFormat; + } + public String getInstallUrl() { return installUrl; } @@ -903,6 +915,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 +993,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() { } @@ -963,6 +1026,10 @@ public String makeDataVolumeInstallUrl(String volUuid) { return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeDataVolumeInstallPath(volUuid)); } + public String makeNvRamVolumeInstallUrl(String volUuid) { + return PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.makeNvRamVolumeInstallPath(volUuid)); + } + public boolean isCachedImageUrl(String path){ return path.startsWith(PathUtil.join(self.getUrl(), PrimaryStoragePathMaker.getCachedImageInstallDir())); } @@ -1254,7 +1321,7 @@ public void success(VolumeStats returnValue) { VolumeInventory vol = msg.getVolume(); vol.setInstallPath(returnValue.getInstallPath()); vol.setActualSize(returnValue.getActualSize()); - vol.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2); + vol.setFormat(returnValue.getFormat()); if (returnValue.getSize() != null) { vol.setSize(returnValue.getSize()); } @@ -1279,7 +1346,9 @@ public void createEmptyVolumeWithBackingFile(final VolumeInventory volume, final cmd.setAccountUuid(acntMgr.getOwnerAccountUuidOfResource(volume.getUuid())); if (volume.getInstallPath() != null && !volume.getInstallPath().equals("")) { cmd.setInstallUrl(volume.getInstallPath()); + cmd.setVolumeFormat(ImageConstant.QCOW2_FORMAT_STRING); } else { + cmd.setVolumeFormat(ImageConstant.QCOW2_FORMAT_STRING); if (VolumeType.Root.toString().equals(volume.getType())) { cmd.setInstallUrl(makeRootVolumeInstallUrl(volume)); } else if (VolumeType.Data.toString().equals(volume.getType())) { @@ -1298,7 +1367,9 @@ public void createEmptyVolumeWithBackingFile(final VolumeInventory volume, final httpCall(CREATE_EMPTY_VOLUME_PATH, hostUuid, cmd, CreateEmptyVolumeRsp.class, new ReturnValueCompletion(completion) { @Override public void success(CreateEmptyVolumeRsp returnValue) { - completion.success(new VolumeStats(cmd.getInstallUrl(), returnValue.actualSize, returnValue.size)); + final VolumeStats stats = new VolumeStats(cmd.getInstallUrl(), returnValue.actualSize, returnValue.size); + stats.setFormat(cmd.getVolumeFormat()); + completion.success(stats); } @Override @@ -3797,4 +3868,117 @@ 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(); + if (rsp.metadataEntries != null) { + List entries = rsp.metadataEntries.stream() + .filter(Objects::nonNull) + .peek(entry -> entry.setHostUuid(hostUuid)) + .collect(Collectors.toList()); + reply.setVmInstanceMetadata(entries); + } else { + reply.setVmInstanceMetadata(Collections.emptyList()); + } + 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..f186aac0dab 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,54 @@ 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; + } + + @RequestMapping(value=LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH, method= RequestMethod.POST) + public @ResponseBody + String prefixRebaseBackingFiles(HttpEntity entity) { + PrefixRebaseBackingFilesCmd cmd = JSONObjectUtil.toObject(entity.getBody(), PrefixRebaseBackingFilesCmd.class); + PrefixRebaseBackingFilesRsp rsp = new PrefixRebaseBackingFilesRsp(); + rsp.rebasedCount = cmd.filePaths == null ? 0 : cmd.filePaths.size(); + reply(entity, rsp); + 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..3f89d4dedff --- /dev/null +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageVmMetadataExtension.java @@ -0,0 +1,179 @@ +package org.zstack.storage.primary.local; + +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.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; +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; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +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", normalizeBaseDir(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", normalizeBaseDir(url), VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX); + } + + @Override + public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) { + if (allOldPaths == null || allOldPaths.isEmpty()) { + PathReplacementResult result = new PathReplacementResult(); + result.setMetadataToCurrentPathMap(Collections.emptyMap()); + return result; + } + + 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.setMetadataToCurrentPathMap(Collections.emptyMap()); + return result; + } + String newPrefix = normalizeBaseDir(baseDir) + "/"; + + // Extract old prefix from the first recognizable path + String oldPrefix = null; + for (String path : allOldPaths) { + oldPrefix = VmInstanceMetadataConstants.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.setMetadataToCurrentPathMap(pathMap); + result.setOldPrefix(oldPrefix); + result.setNewPrefix(newPrefix); + return result; + } + + @Override + public boolean requireHostForCleanup() { + return true; + } + + @Override + public String validateMetadataPath(String primaryStorageUuid, String path) { + if (path == null) { + return "metadataPath cannot be null"; + } + + // Expected format: /.vmmeta + // e.g. /local_ps/vm-metadata/a1b2c3d4e5f6...vmmeta + final String metadataDir; + try { + metadataDir = buildMetadataDir(primaryStorageUuid); + } catch (CloudRuntimeException e) { + return String.format("cannot resolve metadata dir for primary storage[uuid:%s]: %s", + primaryStorageUuid, e.getMessage()); + } + + String prefix = metadataDir + "/"; + String suffix = VmInstanceMetadataConstants.FILE_METADATA_SUFFIX; + + if (!path.startsWith(prefix) || !path.endsWith(suffix)) { + return String.format("metadataPath[%s] does not match expected format: %s/%s", + path, metadataDir, suffix); + } + + String uuidPart = path.substring(prefix.length(), path.length() - suffix.length()); + if (!uuidPart.matches("[0-9a-fA-F]{32}")) { + return String.format("metadataPath[%s] contains invalid uuid[%s], expected 32-char hex", path, uuidPart); + } + return null; + } + + private String normalizeBaseDir(String url) { + return url.endsWith("/") ? url.substring(0, url.length() - 1) : url; + } + + @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) { + boolean exists = Q.New(LocalStorageResourceRefVO.class).eq(LocalStorageResourceRefVO_.resourceUuid, resourceUuid).isExists(); + if (exists) { + logger.debug(String.format("LocalStorageResourceRefVO for resource[uuid:%s] already exists, skip creation", resourceUuid)); + return; + } + + 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); + } + + @Override + public void afterRegistrationRollback(List resourceUuids) { + if (resourceUuids == null || resourceUuids.isEmpty()) { + return; + } + SQL.New(LocalStorageResourceRefVO.class) + .in(LocalStorageResourceRefVO_.resourceUuid, resourceUuids) + .hardDelete(); + } +} 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..6b0142e5a63 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,7 +36,6 @@ 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; @@ -44,23 +43,27 @@ import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmInstanceVO_; +import org.zstack.header.vm.metadata.*; import org.zstack.header.volume.*; -import org.zstack.kvm.*; -import org.zstack.storage.primary.*; +import org.zstack.kvm.KVMConstant; +import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg; +import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply; +import org.zstack.storage.primary.PrimaryStorageBase; +import org.zstack.storage.primary.PrimaryStorageCapacityUpdater; import org.zstack.storage.snapshot.reference.VolumeSnapshotReferenceUtils; 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; import javax.persistence.TypedQuery; import java.io.File; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -131,6 +134,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); } @@ -1896,6 +1901,9 @@ public void fail(ErrorCode errorCode) { private String getHostUuidFromVolume(String volumeUuid) { VolumeVO vol = dbf.findByUuid(volumeUuid, VolumeVO.class); + if (vol == null) { + return ""; + } String hostUuid = ""; List connectedHost = factory.getConnectedHostForOperation(getSelfInventory()); @@ -1924,4 +1932,161 @@ private String getHostUuidFromVolume(String volumeUuid) { return hostUuid; } + + @Override + protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("update-metadata-on-ps-%s", self.getUuid()); + } + + @Override + public int getSyncLevel() { + return 10; + } + + @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 String.format("update-metadata-on-ps-%s", 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 = msg.getHostUuid(); + String finalHostUuid = hostUuid; + if (hostUuid == null || !connectedHosts.stream().anyMatch(h -> h.getUuid().equals(finalHostUuid))) { + hostUuid = connectedHosts.get(0).getUuid(); + } + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); + 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 = getBackendByHostUuid(hostUuid); + 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 = msg.getHostUuid(); + String finalHostUuid = hostUuid; + if (hostUuid == null || !connectedHosts.stream().anyMatch(h -> h.getUuid().equals(finalHostUuid))) { + hostUuid = connectedHosts.get(0).getUuid(); + } + final NfsPrimaryStorageBackend backend = getBackendByHostUuid(hostUuid); + 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); + } + }); + } + + @Override + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + 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 = getBackendByHostUuid(hostUuid); + 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..445497e0871 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; @@ -1139,7 +1148,7 @@ public void run(MessageReply reply) { } volume.setInstallPath(cmd.getInstallUrl()); - volume.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2); + volume.setFormat(cmd.getVolumeFormat()); volume.setActualSize(rsp.actualSize); if (rsp.size != null) { volume.setSize(rsp.size); @@ -2051,4 +2060,174 @@ public void run(MessageReply r) { } }); } + + @Override + public void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + WriteVmMetadataCmd cmd = new WriteVmMetadataCmd(); + cmd.setUuid(msg.getPrimaryStorageUuid()); + 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.setUuid(msg.getPrimaryStorageUuid()); + 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.setUuid(msg.getPrimaryStorageUuid()); + 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(); + if (rsp.metadataEntries != null) { + r.setVmInstanceMetadata(rsp.metadataEntries.stream().filter(Objects::nonNull).collect(Collectors.toList())); + } else { + r.setVmInstanceMetadata(Collections.emptyList()); + } + completion.success(r); + } + }); + } + + @Override + public void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion) { + CleanupVmMetadataCmd cmd = new CleanupVmMetadataCmd(); + cmd.setUuid(msg.getPrimaryStorageUuid()); + cmd.metadataPath = msg.getMetadataPath(); + + KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg(); + hmsg.setCommand(cmd); + hmsg.setPath(CLEANUP_VM_METADATA_PATH); + hmsg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, hostUuid); + 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(); + 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..0901af4cadb 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,8 @@ import org.zstack.header.HasThreadContext; import org.zstack.header.core.validation.Validation; +import org.zstack.header.volume.VolumeConstant; +import org.zstack.header.storage.primary.VmMetadataScanEntry; import org.zstack.kvm.KVMAgentCommands; import org.zstack.kvm.KVMAgentCommands.AgentCommand; import org.zstack.kvm.KVMAgentCommands.AgentResponse; @@ -319,6 +321,7 @@ public abstract static class CreateVolumeCmd extends NfsPrimaryStorageAgentComma private String hypervisorType; private String name; private String volumeUuid; + private String volumeFormat = VolumeConstant.VOLUME_FORMAT_QCOW2; protected long virtualSize; public String getInstallUrl() { @@ -351,6 +354,12 @@ public String getVolumeUuid() { public void setVolumeUuid(String uuid) { this.volumeUuid = uuid; } + public String getVolumeFormat() { + return volumeFormat; + } + public void setVolumeFormat(String volumeFormat) { + this.volumeFormat = volumeFormat; + } public long getVirtualSize() { return virtualSize; } @@ -948,4 +957,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/NfsPrimaryStorageKvmHelper.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKvmHelper.java index a58456a47db..5de2022ebc1 100755 --- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKvmHelper.java +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKvmHelper.java @@ -44,6 +44,10 @@ public static String makeCachedImageInstallUrl(PrimaryStorageInventory pinv, Ima return ImageCacheUtil.getImageCachePath(iminv, it -> PathUtil.join(pinv.getMountPath(), PrimaryStoragePathMaker.makeCachedImageInstallPath(iminv))); } + public static String makeNvRamVolumeInstallUrl(PrimaryStorageInventory pinv, String volUuid) { + return PathUtil.join(pinv.getMountPath(), PrimaryStoragePathMaker.makeNvRamVolumeInstallPath(volUuid)); + } + public static String getCachedImageDir(PrimaryStorageInventory pinv){ return PathUtil.join(pinv.getMountPath(), PrimaryStoragePathMaker.getCachedImageInstallDir()); } 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..db66e11dfe3 --- /dev/null +++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsVmMetadataExtension.java @@ -0,0 +1,125 @@ +package org.zstack.storage.primary.nfs; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Configurable; +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.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +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; + } + + private String resolveBaseDir(String primaryStorageUuid) { + // mountPath is the host-local mount point (e.g. /mnt/pss); + // url is the remote NFS export (e.g. 192.168.1.1:/nfs/share) and must NOT be used + // as a file-system path — it would generate paths the agent cannot access. + String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, primaryStorageUuid).findValue(); + if (baseDir == null) { + throw new CloudRuntimeException(String.format("cannot find mountPath for NFS primary storage[uuid:%s]", primaryStorageUuid)); + } + return baseDir.endsWith("/") ? baseDir.substring(0, baseDir.length() - 1) : baseDir; + } + + @Override + public String buildMetadataDir(String primaryStorageUuid) { + String baseDir = resolveBaseDir(primaryStorageUuid); + return String.format("%s/%s", baseDir, VmInstanceMetadataConstants.METADATA_DIR_NAME); + } + + @Override + public String buildVmMetadataPath(String primaryStorageUuid, String vmInstanceUuid) { + String baseDir = resolveBaseDir(primaryStorageUuid); + return String.format("%s/%s/%s%s", baseDir, VmInstanceMetadataConstants.METADATA_DIR_NAME, vmInstanceUuid, VmInstanceMetadataConstants.FILE_METADATA_SUFFIX); + } + + @Override + public String validateMetadataPath(String primaryStorageUuid, String path) { + if (path == null) { + return "metadataPath cannot be null"; + } + + // Expected format: /.vmmeta + // e.g. /mnt/pss/vm-metadata/a1b2c3d4e5f6...vmmeta + final String metadataDir; + try { + metadataDir = buildMetadataDir(primaryStorageUuid); + } catch (CloudRuntimeException e) { + return String.format("cannot resolve metadata dir for primary storage[uuid:%s]: %s", + primaryStorageUuid, e.getMessage()); + } + + String prefix = metadataDir + "/"; + String suffix = VmInstanceMetadataConstants.FILE_METADATA_SUFFIX; + + if (!path.startsWith(prefix) || !path.endsWith(suffix)) { + return String.format("metadataPath[%s] does not match expected format: %s/%s", + path, metadataDir, suffix); + } + + String uuidPart = path.substring(prefix.length(), path.length() - suffix.length()); + if (!uuidPart.matches("[0-9a-fA-F]{32}")) { + return String.format("metadataPath[%s] contains invalid uuid[%s], expected 32-char hex", path, uuidPart); + } + return null; + } + + @Override + public PathReplacementResult calculatePathReplacements(String targetPsUuid, List allOldPaths) { + if (allOldPaths == null || allOldPaths.isEmpty()) { + PathReplacementResult result = new PathReplacementResult(); + result.setMetadataToCurrentPathMap(Collections.emptyMap()); + return result; + } + + String baseDir = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.mountPath).eq(PrimaryStorageVO_.uuid, targetPsUuid).findValue(); + if (baseDir == null) { + logger.warn(String.format("NFS PS[uuid:%s] has no mountPath, path replacement disabled", targetPsUuid)); + PathReplacementResult result = new PathReplacementResult(); + result.setMetadataToCurrentPathMap(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 = VmInstanceMetadataConstants.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.setMetadataToCurrentPathMap(pathMap); + result.setOldPrefix(oldPrefix); + result.setNewPrefix(newPrefix); + return result; + } +} diff --git a/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java b/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java index ee7faa6c40c..a1e0d10bf8b 100755 --- a/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java +++ b/plugin/sharedMountPointPrimaryStorage/src/main/java/org/zstack/storage/primary/smp/KvmBackend.java @@ -321,6 +321,7 @@ public static class CreateEmptyVolumeCmd extends AgentCmd { public String name; public String volumeUuid; public String backingFile; + public String volumeFormat = VolumeConstant.VOLUME_FORMAT_QCOW2; } public static class CreateEmptyVolumeRsp extends AgentRsp { @@ -617,6 +618,10 @@ public String makeCachedImageInstallUrl(ImageInventory iminv) { return ImageCacheUtil.getImageCachePath(iminv, it -> PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeCachedImageInstallPath(iminv))); } + public String makeNvRamVolumeInstallUrl(String volUuid) { + return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeNvRamVolumeInstallPath(volUuid)); + } + public String makeCachedImageInstallUrlFromImageUuidForTemplate(String imageUuid) { return PathUtil.join(self.getMountPath(), PrimaryStoragePathMaker.makeCachedImageInstallPathFromImageUuidForTemplate(imageUuid)); } @@ -982,7 +987,7 @@ public void success(AgentRsp returnValue) { CreateEmptyVolumeRsp rsp = (CreateEmptyVolumeRsp) returnValue; InstantiateVolumeOnPrimaryStorageReply reply = new InstantiateVolumeOnPrimaryStorageReply(); volume.setInstallPath(cmd.installPath); - volume.setFormat(VolumeConstant.VOLUME_FORMAT_QCOW2); + volume.setFormat(cmd.volumeFormat); volume.setActualSize(rsp.actualSize); if (rsp.size != null) { volume.setSize(rsp.size); diff --git a/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java b/resourceconfig/src/main/java/org/zstack/resourceconfig/APIDeleteResourceConfigMsg.java index fd43d66dac3..7733f7702b0 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 = "ResourceUuidToVmUuidResolver", field = "resourceUuid") 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..fc30ba61e3b 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 = "ResourceUuidToVmUuidResolver", field = "resourceUuid") public class APIUpdateResourceConfigMsg extends APIMessage implements ResourceConfigMessage { @APIParam private String category; diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 146b132c459..86c9a8050b5 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -70,6 +70,9 @@ public class SourceClassMap { put("org.zstack.core.jsonlabel.JsonLabelInventory", "org.zstack.sdk.JsonLabelInventory"); put("org.zstack.crypto.ccs.CCSCertificateAccountRefInventory", "org.zstack.sdk.CCSCertificateAccountRefInventory"); put("org.zstack.crypto.ccs.CCSCertificateInventory", "org.zstack.sdk.CCSCertificateInventory"); + put("org.zstack.crypto.keyprovider.api.RekeyFailedResource", "org.zstack.sdk.keyprovider.api.RekeyFailedResource"); + put("org.zstack.crypto.keyprovider.api.RekeyProviderResult", "org.zstack.sdk.keyprovider.api.RekeyProviderResult"); + put("org.zstack.crypto.keyprovider.api.RekeySkippedResource", "org.zstack.sdk.keyprovider.api.RekeySkippedResource"); put("org.zstack.crypto.securitymachine.thirdparty.aisino.AiSiNoSecretResourcePoolInventory", "org.zstack.sdk.AiSiNoSecretResourcePoolInventory"); put("org.zstack.crypto.securitymachine.thirdparty.flkSec.FlkSecSecretResourcePoolInventory", "org.zstack.sdk.FlkSecSecretResourcePoolInventory"); put("org.zstack.crypto.securitymachine.thirdparty.flkSec.FlkSecSecurityMachineInventory", "org.zstack.sdk.FlkSecSecurityMachineInventory"); @@ -178,6 +181,12 @@ public class SourceClassMap { put("org.zstack.header.image.APIGetUploadImageJobDetailsReply$JobDetails", "org.zstack.sdk.JobDetails"); put("org.zstack.header.image.ImageBackupStorageRefInventory", "org.zstack.sdk.ImageBackupStorageRefInventory"); put("org.zstack.header.image.ImageInventory", "org.zstack.sdk.ImageInventory"); + put("org.zstack.header.keyprovider.CertificateInfo", "org.zstack.sdk.CertificateInfo"); + put("org.zstack.header.keyprovider.KeyProviderInventory", "org.zstack.sdk.KeyProviderInventory"); + put("org.zstack.header.keyprovider.KmsIdentityInventory", "org.zstack.sdk.KmsIdentityInventory"); + put("org.zstack.header.keyprovider.KmsInventory", "org.zstack.sdk.KmsInventory"); + put("org.zstack.header.keyprovider.NkpInventory", "org.zstack.sdk.NkpInventory"); + put("org.zstack.header.keyprovider.NkpRestoreInfo", "org.zstack.sdk.NkpRestoreInfo"); put("org.zstack.header.longjob.LongJobInventory", "org.zstack.sdk.LongJobInventory"); put("org.zstack.header.longjob.LongJobState", "org.zstack.sdk.LongJobState"); put("org.zstack.header.managementnode.ManagementNodeInventory", "org.zstack.sdk.ManagementNodeInventory"); @@ -243,10 +252,13 @@ public class SourceClassMap { put("org.zstack.header.storage.database.backup.DatabaseBackupStorageRefInventory", "org.zstack.sdk.databasebackup.DatabaseBackupStorageRefInventory"); put("org.zstack.header.storage.database.backup.DatabaseBackupStruct", "org.zstack.sdk.databasebackup.DatabaseBackupStruct"); put("org.zstack.header.storage.database.backup.DatabaseType", "org.zstack.sdk.databasebackup.DatabaseType"); + put("org.zstack.header.storage.primary.ConsistencyCheckStatus", "org.zstack.sdk.ConsistencyCheckStatus"); put("org.zstack.header.storage.primary.ImageCacheInventory", "org.zstack.sdk.ImageCacheInventory"); put("org.zstack.header.storage.primary.PrimaryStorageHostStatus", "org.zstack.sdk.PrimaryStorageHostStatus"); put("org.zstack.header.storage.primary.PrimaryStorageInventory", "org.zstack.sdk.PrimaryStorageInventory"); + put("org.zstack.header.storage.primary.ReconnectResult", "org.zstack.sdk.ReconnectResult"); put("org.zstack.header.storage.primary.UsageReport", "org.zstack.sdk.UsageReport"); + put("org.zstack.header.storage.primary.VmMetadataScanEntry", "org.zstack.sdk.VmMetadataScanEntry"); put("org.zstack.header.storage.snapshot.BatchDeleteVolumeSnapshotStruct", "org.zstack.sdk.BatchDeleteVolumeSnapshotStruct"); put("org.zstack.header.storage.snapshot.ShrinkResult", "org.zstack.sdk.ShrinkResult"); put("org.zstack.header.storage.snapshot.VolumeSnapshotBackupStorageRefInventory", "org.zstack.sdk.VolumeSnapshotBackupStorageRefInventory"); @@ -270,6 +282,8 @@ public class SourceClassMap { put("org.zstack.header.tag.TagPatternInventory", "org.zstack.sdk.TagPatternInventory"); put("org.zstack.header.tag.TagPatternType", "org.zstack.sdk.TagPatternType"); put("org.zstack.header.tag.UserTagInventory", "org.zstack.sdk.UserTagInventory"); + put("org.zstack.header.tpm.entity.TpmCapabilityView", "org.zstack.sdk.tpm.entity.TpmCapabilityView"); + put("org.zstack.header.tpm.entity.TpmInventory", "org.zstack.sdk.tpm.entity.TpmInventory"); put("org.zstack.header.vdpa.VmVdpaNicInventory", "org.zstack.sdk.VmVdpaNicInventory"); put("org.zstack.header.vipQos.VipQosInventory", "org.zstack.sdk.VipQosInventory"); put("org.zstack.header.vm.CloneVmInstanceInventory", "org.zstack.sdk.CloneVmInstanceInventory"); @@ -286,6 +300,7 @@ public class SourceClassMap { put("org.zstack.header.vm.VmPriorityConfigInventory", "org.zstack.sdk.VmPriorityConfigInventory"); put("org.zstack.header.vm.VmPriorityLevel", "org.zstack.sdk.VmPriorityLevel"); put("org.zstack.header.vm.VmSchedHistoryInventory", "org.zstack.sdk.VmSchedHistoryInventory"); + put("org.zstack.header.vm.additions.VmHostFileInventory", "org.zstack.sdk.vm.entity.VmHostFileInventory"); put("org.zstack.header.vm.cdrom.VmCdRomInventory", "org.zstack.sdk.VmCdRomInventory"); put("org.zstack.header.vm.devices.DeviceAddress", "org.zstack.sdk.DeviceAddress"); put("org.zstack.header.vm.devices.VmInstanceResourceMetadataArchiveInventory", "org.zstack.sdk.VmInstanceResourceMetadataArchiveInventory"); @@ -555,6 +570,7 @@ public class SourceClassMap { put("org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageHostRefInventory", "org.zstack.sdk.SharedBlockGroupPrimaryStorageHostRefInventory"); put("org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageInventory", "org.zstack.sdk.SharedBlockGroupPrimaryStorageInventory"); put("org.zstack.storage.primary.sharedblock.SharedBlockGroupType", "org.zstack.sdk.SharedBlockGroupType"); + put("org.zstack.storage.primary.sharedblock.SharedBlockGroupVgInfo", "org.zstack.sdk.SharedBlockGroupVgInfo"); put("org.zstack.storage.primary.sharedblock.SharedBlockInventory", "org.zstack.sdk.SharedBlockInventory"); put("org.zstack.storage.primary.sharedblock.SharedBlockState", "org.zstack.sdk.SharedBlockState"); put("org.zstack.storage.primary.sharedblock.SharedBlockStatus", "org.zstack.sdk.SharedBlockStatus"); @@ -755,6 +771,7 @@ public class SourceClassMap { put("org.zstack.sdk.CephPrimaryStorageInventory", "org.zstack.storage.ceph.primary.CephPrimaryStorageInventory"); put("org.zstack.sdk.CephPrimaryStorageMonInventory", "org.zstack.storage.ceph.primary.CephPrimaryStorageMonInventory"); put("org.zstack.sdk.CephPrimaryStoragePoolInventory", "org.zstack.storage.ceph.primary.CephPrimaryStoragePoolInventory"); + put("org.zstack.sdk.CertificateInfo", "org.zstack.header.keyprovider.CertificateInfo"); put("org.zstack.sdk.CertificateInventory", "org.zstack.network.service.lb.CertificateInventory"); put("org.zstack.sdk.ChainInfo", "org.zstack.header.core.progress.ChainInfo"); put("org.zstack.sdk.ChronyServerInfo", "org.zstack.zops.ChronyServerInfo"); @@ -765,6 +782,7 @@ public class SourceClassMap { put("org.zstack.sdk.CloudFormationStackEventInventory", "org.zstack.header.cloudformation.CloudFormationStackEventInventory"); put("org.zstack.sdk.ClusterDRSInventory", "org.zstack.drs.entity.ClusterDRSInventory"); put("org.zstack.sdk.ClusterInventory", "org.zstack.header.cluster.ClusterInventory"); + put("org.zstack.sdk.ConsistencyCheckStatus", "org.zstack.header.storage.primary.ConsistencyCheckStatus"); put("org.zstack.sdk.ConsoleInventory", "org.zstack.header.console.ConsoleInventory"); put("org.zstack.sdk.ConsoleProxyAgentInventory", "org.zstack.header.console.ConsoleProxyAgentInventory"); put("org.zstack.sdk.ControlStrategy", "org.zstack.loginControl.entity.ControlStrategy"); @@ -879,6 +897,9 @@ public class SourceClassMap { put("org.zstack.sdk.KVMCephVolumeTO", "org.zstack.storage.ceph.primary.KVMCephVolumeTO"); put("org.zstack.sdk.KVMHostInventory", "org.zstack.kvm.KVMHostInventory"); put("org.zstack.sdk.KVMIsoTO", "org.zstack.kvm.KVMIsoTO"); + put("org.zstack.sdk.KeyProviderInventory", "org.zstack.header.keyprovider.KeyProviderInventory"); + put("org.zstack.sdk.KmsIdentityInventory", "org.zstack.header.keyprovider.KmsIdentityInventory"); + put("org.zstack.sdk.KmsInventory", "org.zstack.header.keyprovider.KmsInventory"); put("org.zstack.sdk.KvmCephCdRomTO", "org.zstack.storage.ceph.primary.KvmCephCdRomTO"); put("org.zstack.sdk.KvmCephIsoTO", "org.zstack.storage.ceph.primary.KvmCephIsoTO"); put("org.zstack.sdk.KvmHostHypervisorMetadataInventory", "org.zstack.kvm.hypervisor.datatype.KvmHostHypervisorMetadataInventory"); @@ -948,6 +969,8 @@ public class SourceClassMap { put("org.zstack.sdk.NetworkServiceL3NetworkRefInventory", "org.zstack.header.network.service.NetworkServiceL3NetworkRefInventory"); put("org.zstack.sdk.NetworkServiceProviderInventory", "org.zstack.header.network.service.NetworkServiceProviderInventory"); put("org.zstack.sdk.NicTO", "org.zstack.kvm.KVMAgentCommands$NicTO"); + put("org.zstack.sdk.NkpInventory", "org.zstack.header.keyprovider.NkpInventory"); + put("org.zstack.sdk.NkpRestoreInfo", "org.zstack.header.keyprovider.NkpRestoreInfo"); put("org.zstack.sdk.NormalIpRangeInventory", "org.zstack.header.network.l3.NormalIpRangeInventory"); put("org.zstack.sdk.NvmeLunHostRefInventory", "org.zstack.storage.device.nvme.NvmeLunHostRefInventory"); put("org.zstack.sdk.NvmeLunInventory", "org.zstack.storage.device.nvme.NvmeLunInventory"); @@ -1025,6 +1048,7 @@ public class SourceClassMap { put("org.zstack.sdk.QuotaUsage", "org.zstack.header.identity.Quota$QuotaUsage"); put("org.zstack.sdk.RaidControllerInventory", "org.zstack.storage.device.localRaid.RaidControllerInventory"); put("org.zstack.sdk.RaidPhysicalDriveInventory", "org.zstack.storage.device.localRaid.RaidPhysicalDriveInventory"); + put("org.zstack.sdk.ReconnectResult", "org.zstack.header.storage.primary.ReconnectResult"); put("org.zstack.sdk.RedirectUrlTemplate", "org.zstack.sso.header.RedirectUrlTemplate"); put("org.zstack.sdk.RemoteVtepInventory", "org.zstack.network.l2.vxlan.vtep.RemoteVtepInventory"); put("org.zstack.sdk.RemovalInstanceRuleInventory", "org.zstack.autoscaling.group.rule.RemovalInstanceRuleInventory"); @@ -1078,6 +1102,7 @@ public class SourceClassMap { put("org.zstack.sdk.SharedBlockGroupPrimaryStorageHostRefInventory", "org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageHostRefInventory"); put("org.zstack.sdk.SharedBlockGroupPrimaryStorageInventory", "org.zstack.storage.primary.sharedblock.SharedBlockGroupPrimaryStorageInventory"); put("org.zstack.sdk.SharedBlockGroupType", "org.zstack.storage.primary.sharedblock.SharedBlockGroupType"); + put("org.zstack.sdk.SharedBlockGroupVgInfo", "org.zstack.storage.primary.sharedblock.SharedBlockGroupVgInfo"); put("org.zstack.sdk.SharedBlockInventory", "org.zstack.storage.primary.sharedblock.SharedBlockInventory"); put("org.zstack.sdk.SharedBlockState", "org.zstack.storage.primary.sharedblock.SharedBlockState"); put("org.zstack.sdk.SharedBlockStatus", "org.zstack.storage.primary.sharedblock.SharedBlockStatus"); @@ -1163,6 +1188,7 @@ public class SourceClassMap { put("org.zstack.sdk.VmInstanceResourceMetadataGroupInventory", "org.zstack.header.vm.devices.VmInstanceResourceMetadataGroupInventory"); put("org.zstack.sdk.VmMemoryBillingInventory", "org.zstack.billing.generator.vm.memory.VmMemoryBillingInventory"); put("org.zstack.sdk.VmMemorySpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmMemorySpendingDetails"); + put("org.zstack.sdk.VmMetadataScanEntry", "org.zstack.header.storage.primary.VmMetadataScanEntry"); put("org.zstack.sdk.VmNicBandwidthSpendingDetails", "org.zstack.billing.spendingcalculator.vmnic.VmNicBandwidthSpendingDetails"); put("org.zstack.sdk.VmNicConflictEntry", "org.zstack.header.storage.snapshot.group.VmNicConflictEntry"); put("org.zstack.sdk.VmNicInventory", "org.zstack.header.vm.VmNicInventory"); @@ -1243,6 +1269,9 @@ public class SourceClassMap { put("org.zstack.sdk.identity.ldap.entity.LdapServerInventory", "org.zstack.ldap.entity.LdapServerInventory"); put("org.zstack.sdk.identity.role.RoleAccountRefInventory", "org.zstack.header.identity.role.RoleAccountRefInventory"); put("org.zstack.sdk.identity.role.RoleInventory", "org.zstack.header.identity.role.RoleInventory"); + put("org.zstack.sdk.keyprovider.api.RekeyFailedResource", "org.zstack.crypto.keyprovider.api.RekeyFailedResource"); + put("org.zstack.sdk.keyprovider.api.RekeyProviderResult", "org.zstack.crypto.keyprovider.api.RekeyProviderResult"); + put("org.zstack.sdk.keyprovider.api.RekeySkippedResource", "org.zstack.crypto.keyprovider.api.RekeySkippedResource"); put("org.zstack.sdk.license.entity.LicenseHistoryInventory", "org.zstack.license.entity.LicenseHistoryInventory"); put("org.zstack.sdk.license.entity.LicenseUsageView", "org.zstack.license.entity.LicenseUsageView"); put("org.zstack.sdk.license.entity.UpdateLicenseView", "org.zstack.license.entity.UpdateLicenseView"); @@ -1270,6 +1299,9 @@ public class SourceClassMap { put("org.zstack.sdk.sns.platform.wecom.SNSWeComEndpointInventory", "org.zstack.sns.platform.wecom.SNSWeComEndpointInventory"); put("org.zstack.sdk.softwarePackage.header.JobDetails", "org.zstack.softwarePackage.header.JobDetails"); put("org.zstack.sdk.softwarePackage.header.SoftwarePackageInventory", "org.zstack.softwarePackage.header.SoftwarePackageInventory"); + put("org.zstack.sdk.tpm.entity.TpmCapabilityView", "org.zstack.header.tpm.entity.TpmCapabilityView"); + put("org.zstack.sdk.tpm.entity.TpmInventory", "org.zstack.header.tpm.entity.TpmInventory"); + put("org.zstack.sdk.vm.entity.VmHostFileInventory", "org.zstack.header.vm.additions.VmHostFileInventory"); put("org.zstack.sdk.zbox.ZBoxBackupInventory", "org.zstack.externalbackup.zbox.ZBoxBackupInventory"); put("org.zstack.sdk.zbox.ZBoxBackupStorageBackupInfo", "org.zstack.externalbackup.zbox.ZBoxBackupStorageBackupInfo"); put("org.zstack.sdk.zbox.ZBoxInventory", "org.zstack.zbox.ZBoxInventory"); diff --git a/sdk/src/main/java/org/zstack/heder/storage/volume/backup/CreateVmFromVolumeBackupAction.java b/sdk/src/main/java/org/zstack/heder/storage/volume/backup/CreateVmFromVolumeBackupAction.java index 1a56f081ab4..a1a9af07824 100644 --- a/sdk/src/main/java/org/zstack/heder/storage/volume/backup/CreateVmFromVolumeBackupAction.java +++ b/sdk/src/main/java/org/zstack/heder/storage/volume/backup/CreateVmFromVolumeBackupAction.java @@ -73,6 +73,9 @@ public Result throwExceptionIfError() { @Param(required = false, validValues = {"InstantStart","JustCreate","CreateStopped"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String strategy = "InstantStart"; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean resetTpm; + @Param(required = false) public java.lang.String resourceUuid; diff --git a/sdk/src/main/java/org/zstack/sdk/CertificateInfo.java b/sdk/src/main/java/org/zstack/sdk/CertificateInfo.java new file mode 100644 index 00000000000..5dc36854ed1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CertificateInfo.java @@ -0,0 +1,55 @@ +package org.zstack.sdk; + + + +public class CertificateInfo { + + public java.lang.String subject; + public void setSubject(java.lang.String subject) { + this.subject = subject; + } + public java.lang.String getSubject() { + return this.subject; + } + + public java.lang.String issuer; + public void setIssuer(java.lang.String issuer) { + this.issuer = issuer; + } + public java.lang.String getIssuer() { + return this.issuer; + } + + public java.lang.String commonName; + public void setCommonName(java.lang.String commonName) { + this.commonName = commonName; + } + public java.lang.String getCommonName() { + return this.commonName; + } + + public java.util.List subjectAltNamesDns; + public void setSubjectAltNamesDns(java.util.List subjectAltNamesDns) { + this.subjectAltNamesDns = subjectAltNamesDns; + } + public java.util.List getSubjectAltNamesDns() { + return this.subjectAltNamesDns; + } + + public java.util.List subjectAltNamesIp; + public void setSubjectAltNamesIp(java.util.List subjectAltNamesIp) { + this.subjectAltNamesIp = subjectAltNamesIp; + } + public java.util.List getSubjectAltNamesIp() { + return this.subjectAltNamesIp; + } + + public java.sql.Timestamp expiredDate; + public void setExpiredDate(java.sql.Timestamp expiredDate) { + this.expiredDate = expiredDate; + } + public java.sql.Timestamp getExpiredDate() { + return this.expiredDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java new file mode 100644 index 00000000000..5c86726b2d3 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyAction.java @@ -0,0 +1,95 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CheckPrimaryStorageConsistencyAction 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.CheckPrimaryStorageConsistencyResult 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.CheckPrimaryStorageConsistencyResult value = res.getResult(org.zstack.sdk.CheckPrimaryStorageConsistencyResult.class); + ret.value = value == null ? new org.zstack.sdk.CheckPrimaryStorageConsistencyResult() : 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/{uuid}/consistency"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java new file mode 100644 index 00000000000..f5c4b11d738 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CheckPrimaryStorageConsistencyResult.java @@ -0,0 +1,30 @@ +package org.zstack.sdk; + +import org.zstack.sdk.ConsistencyCheckStatus; + +public class CheckPrimaryStorageConsistencyResult { + public boolean consistent; + public void setConsistent(boolean consistent) { + this.consistent = consistent; + } + public boolean getConsistent() { + return this.consistent; + } + + public ConsistencyCheckStatus status; + public void setStatus(ConsistencyCheckStatus status) { + this.status = status; + } + public ConsistencyCheckStatus getStatus() { + return this.status; + } + + public java.lang.String candidateVgUuid; + public void setCandidateVgUuid(java.lang.String candidateVgUuid) { + this.candidateVgUuid = candidateVgUuid; + } + public java.lang.String getCandidateVgUuid() { + return this.candidateVgUuid; + } + +} 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/CloneVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/CloneVmInstanceAction.java index 23ebc2033ca..661106961c5 100644 --- a/sdk/src/main/java/org/zstack/sdk/CloneVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CloneVmInstanceAction.java @@ -67,6 +67,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public org.zstack.sdk.VmCustomSpecificationStruct vmCustomSpecification; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean resetTpm; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckStatus.java b/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckStatus.java new file mode 100644 index 00000000000..ea21d43736a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ConsistencyCheckStatus.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + +public enum ConsistencyCheckStatus { + CONSISTENT, + UUID_MISMATCH, + VG_NOT_FOUND, +} diff --git a/sdk/src/main/java/org/zstack/sdk/ConvertTemplatedVmInstanceToVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/ConvertTemplatedVmInstanceToVmInstanceAction.java index 1bc3dbc2656..5989a4c96d0 100644 --- a/sdk/src/main/java/org/zstack/sdk/ConvertTemplatedVmInstanceToVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ConvertTemplatedVmInstanceToVmInstanceAction.java @@ -31,6 +31,9 @@ public Result throwExceptionIfError() { @Param(required = true, 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.lang.Boolean resetTpm; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java index 8c05a04e241..f475d3c4761 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceAction.java @@ -123,6 +123,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.util.List diskAOs; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.Map devices; + @Param(required = false) public java.lang.String resourceUuid; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromTemplatedVmInstanceAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromTemplatedVmInstanceAction.java index 8994903a456..4e699ce5481 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromTemplatedVmInstanceAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromTemplatedVmInstanceAction.java @@ -76,6 +76,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public org.zstack.sdk.VmCustomSpecificationStruct vmCustomSpecification; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean resetTpm; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java index 2eb0f1fa80c..7f477d9b0fb 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmInstanceFromVolumeSnapshotGroupAction.java @@ -79,6 +79,9 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.util.Map dataVolumeSystemTags; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean resetTpm; + @Param(required = false) public java.lang.String resourceUuid; diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java new file mode 100644 index 00000000000..e88feb2199e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsAction.java @@ -0,0 +1,95 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class DiscoverSharedBlockGroupVgsAction 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.DiscoverSharedBlockGroupVgsResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s]", error.code, error.description, error.details) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String clusterUuid; + + @Param(required = 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.DiscoverSharedBlockGroupVgsResult value = res.getResult(org.zstack.sdk.DiscoverSharedBlockGroupVgsResult.class); + ret.value = value == null ? new org.zstack.sdk.DiscoverSharedBlockGroupVgsResult() : 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/sharedblockgroup/vgs"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java new file mode 100644 index 00000000000..9d23a4446e7 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DiscoverSharedBlockGroupVgsResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + + + +public class DiscoverSharedBlockGroupVgsResult { + public java.util.Map vgInfos; + public void setVgInfos(java.util.Map vgInfos) { + this.vgInfos = vgInfos; + } + public java.util.Map getVgInfos() { + return this.vgInfos; + } + +} 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..a0d62c682a4 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetVmInstanceMetadataFromPrimaryStorageAction.java @@ -0,0 +1,101 @@ +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; + + @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.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 = true; + 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/KeyProviderInventory.java b/sdk/src/main/java/org/zstack/sdk/KeyProviderInventory.java new file mode 100644 index 00000000000..a2eef45777e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/KeyProviderInventory.java @@ -0,0 +1,63 @@ +package org.zstack.sdk; + + + +public class KeyProviderInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String description; + public void setDescription(java.lang.String description) { + this.description = description; + } + public java.lang.String getDescription() { + return this.description; + } + + public java.lang.String type; + public void setType(java.lang.String type) { + this.type = type; + } + public java.lang.String getType() { + return this.type; + } + + public boolean connected; + public void setConnected(boolean connected) { + this.connected = connected; + } + public boolean getConnected() { + return this.connected; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/KmsIdentityInventory.java b/sdk/src/main/java/org/zstack/sdk/KmsIdentityInventory.java new file mode 100644 index 00000000000..d621aa1d749 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/KmsIdentityInventory.java @@ -0,0 +1,71 @@ +package org.zstack.sdk; + + + +public class KmsIdentityInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String kmsUuid; + public void setKmsUuid(java.lang.String kmsUuid) { + this.kmsUuid = kmsUuid; + } + public java.lang.String getKmsUuid() { + return this.kmsUuid; + } + + public java.lang.String identityType; + public void setIdentityType(java.lang.String identityType) { + this.identityType = identityType; + } + public java.lang.String getIdentityType() { + return this.identityType; + } + + public java.lang.String clientCertPem; + public void setClientCertPem(java.lang.String clientCertPem) { + this.clientCertPem = clientCertPem; + } + public java.lang.String getClientCertPem() { + return this.clientCertPem; + } + + public java.lang.String csrPem; + public void setCsrPem(java.lang.String csrPem) { + this.csrPem = csrPem; + } + public java.lang.String getCsrPem() { + return this.csrPem; + } + + public java.sql.Timestamp certExpiredDate; + public void setCertExpiredDate(java.sql.Timestamp certExpiredDate) { + this.certExpiredDate = certExpiredDate; + } + public java.sql.Timestamp getCertExpiredDate() { + return this.certExpiredDate; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/KmsInventory.java b/sdk/src/main/java/org/zstack/sdk/KmsInventory.java new file mode 100644 index 00000000000..8da5d74fa61 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/KmsInventory.java @@ -0,0 +1,80 @@ +package org.zstack.sdk; + +import org.zstack.sdk.CertificateInfo; +import org.zstack.sdk.KmsIdentityInventory; + +public class KmsInventory extends org.zstack.sdk.KeyProviderInventory { + + public java.lang.String endpoint; + public void setEndpoint(java.lang.String endpoint) { + this.endpoint = endpoint; + } + public java.lang.String getEndpoint() { + return this.endpoint; + } + + public java.lang.Integer port; + public void setPort(java.lang.Integer port) { + this.port = port; + } + public java.lang.Integer getPort() { + return this.port; + } + + public java.lang.String kmipVersion; + public void setKmipVersion(java.lang.String kmipVersion) { + this.kmipVersion = kmipVersion; + } + public java.lang.String getKmipVersion() { + return this.kmipVersion; + } + + public java.lang.String username; + public void setUsername(java.lang.String username) { + this.username = username; + } + public java.lang.String getUsername() { + return this.username; + } + + public java.lang.String trustState; + public void setTrustState(java.lang.String trustState) { + this.trustState = trustState; + } + public java.lang.String getTrustState() { + return this.trustState; + } + + public java.lang.String activeIdentityUuid; + public void setActiveIdentityUuid(java.lang.String activeIdentityUuid) { + this.activeIdentityUuid = activeIdentityUuid; + } + public java.lang.String getActiveIdentityUuid() { + return this.activeIdentityUuid; + } + + public java.lang.String serverCertPem; + public void setServerCertPem(java.lang.String serverCertPem) { + this.serverCertPem = serverCertPem; + } + public java.lang.String getServerCertPem() { + return this.serverCertPem; + } + + public CertificateInfo serverCertInfo; + public void setServerCertInfo(CertificateInfo serverCertInfo) { + this.serverCertInfo = serverCertInfo; + } + public CertificateInfo getServerCertInfo() { + return this.serverCertInfo; + } + + public KmsIdentityInventory activeIdentity; + public void setActiveIdentity(KmsIdentityInventory activeIdentity) { + this.activeIdentity = activeIdentity; + } + public KmsIdentityInventory getActiveIdentity() { + return this.activeIdentity; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/NkpInventory.java b/sdk/src/main/java/org/zstack/sdk/NkpInventory.java new file mode 100644 index 00000000000..b76e38f3313 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/NkpInventory.java @@ -0,0 +1,39 @@ +package org.zstack.sdk; + + + +public class NkpInventory extends org.zstack.sdk.KeyProviderInventory { + + public java.lang.String kdf; + public void setKdf(java.lang.String kdf) { + this.kdf = kdf; + } + public java.lang.String getKdf() { + return this.kdf; + } + + public java.lang.String saltPolicy; + public void setSaltPolicy(java.lang.String saltPolicy) { + this.saltPolicy = saltPolicy; + } + public java.lang.String getSaltPolicy() { + return this.saltPolicy; + } + + public boolean backedUp; + public void setBackedUp(boolean backedUp) { + this.backedUp = backedUp; + } + public boolean getBackedUp() { + return this.backedUp; + } + + public java.lang.Integer currentVersion; + public void setCurrentVersion(java.lang.Integer currentVersion) { + this.currentVersion = currentVersion; + } + public java.lang.Integer getCurrentVersion() { + return this.currentVersion; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/NkpRestoreInfo.java b/sdk/src/main/java/org/zstack/sdk/NkpRestoreInfo.java new file mode 100644 index 00000000000..a9f108dab40 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/NkpRestoreInfo.java @@ -0,0 +1,63 @@ +package org.zstack.sdk; + + + +public class NkpRestoreInfo { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String description; + public void setDescription(java.lang.String description) { + this.description = description; + } + public java.lang.String getDescription() { + return this.description; + } + + public java.lang.String kdf; + public void setKdf(java.lang.String kdf) { + this.kdf = kdf; + } + public java.lang.String getKdf() { + return this.kdf; + } + + public java.lang.String saltPolicy; + public void setSaltPolicy(java.lang.String saltPolicy) { + this.saltPolicy = saltPolicy; + } + public java.lang.String getSaltPolicy() { + return this.saltPolicy; + } + + public java.lang.Integer currentVersion; + public void setCurrentVersion(java.lang.Integer currentVersion) { + this.currentVersion = currentVersion; + } + public java.lang.Integer getCurrentVersion() { + return this.currentVersion; + } + + public java.lang.Long backupTime; + public void setBackupTime(java.lang.Long backupTime) { + this.backupTime = backupTime; + } + public java.lang.Long getBackupTime() { + return this.backupTime; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java b/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java new file mode 100644 index 00000000000..30eadb24630 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + +public enum ReconnectResult { + SUCCESS, + FAILED, + NOT_ATTEMPTED, +} 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..0c7d43cec6b --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterVmInstanceFromMetadataAction.java @@ -0,0 +1,116 @@ +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, maxLength = 2048, nonempty = true, 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 = false, 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) + 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/SharedBlockGroupVgInfo.java b/sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java new file mode 100644 index 00000000000..3bd931409ed --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/SharedBlockGroupVgInfo.java @@ -0,0 +1,31 @@ +package org.zstack.sdk; + + + +public class SharedBlockGroupVgInfo { + + public java.util.List candidateLuns; + public void setCandidateLuns(java.util.List candidateLuns) { + this.candidateLuns = candidateLuns; + } + public java.util.List getCandidateLuns() { + return this.candidateLuns; + } + + public boolean sharedGroupComplete; + public void setSharedGroupComplete(boolean sharedGroupComplete) { + this.sharedGroupComplete = sharedGroupComplete; + } + public boolean getSharedGroupComplete() { + return this.sharedGroupComplete; + } + + public java.util.Map existLunWwidsByHost; + public void setExistLunWwidsByHost(java.util.Map existLunWwidsByHost) { + this.existLunWwidsByHost = existLunWwidsByHost; + } + public java.util.Map getExistLunWwidsByHost() { + return this.existLunWwidsByHost; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java new file mode 100644 index 00000000000..f168002ef64 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class TakeoverPrimaryStorageAction 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.TakeoverPrimaryStorageResult 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; + + @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.TakeoverPrimaryStorageResult value = res.getResult(org.zstack.sdk.TakeoverPrimaryStorageResult.class); + ret.value = value == null ? new org.zstack.sdk.TakeoverPrimaryStorageResult() : 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 = "/primary-storage/{uuid}/takeover"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "takeoverPrimaryStorage"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java new file mode 100644 index 00000000000..6b76df3b5bf --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/TakeoverPrimaryStorageResult.java @@ -0,0 +1,31 @@ +package org.zstack.sdk; + +import org.zstack.sdk.PrimaryStorageInventory; +import org.zstack.sdk.ReconnectResult; + +public class TakeoverPrimaryStorageResult { + public PrimaryStorageInventory inventory; + public void setInventory(PrimaryStorageInventory inventory) { + this.inventory = inventory; + } + public PrimaryStorageInventory getInventory() { + return this.inventory; + } + + public ReconnectResult reconnectResult; + public void setReconnectResult(ReconnectResult reconnectResult) { + this.reconnectResult = reconnectResult; + } + public ReconnectResult getReconnectResult() { + return this.reconnectResult; + } + + public java.lang.String reconnectError; + public void setReconnectError(java.lang.String reconnectError) { + this.reconnectError = reconnectError; + } + public java.lang.String getReconnectError() { + return this.reconnectError; + } + +} 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..2f2620e38e2 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmInstanceMetadataAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +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 = "updateVmInstanceMetadata"; + 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/sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java b/sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java new file mode 100644 index 00000000000..fc5fc13f644 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmMetadataScanEntry.java @@ -0,0 +1,87 @@ +package org.zstack.sdk; + + + +public class VmMetadataScanEntry { + + public java.lang.String vmUuid; + public void setVmUuid(java.lang.String vmUuid) { + this.vmUuid = vmUuid; + } + public java.lang.String getVmUuid() { + return this.vmUuid; + } + + public java.lang.String vmName; + public void setVmName(java.lang.String vmName) { + this.vmName = vmName; + } + public java.lang.String getVmName() { + return this.vmName; + } + + public java.lang.String vmCategory; + public void setVmCategory(java.lang.String vmCategory) { + this.vmCategory = vmCategory; + } + public java.lang.String getVmCategory() { + return this.vmCategory; + } + + public java.lang.String architecture; + public void setArchitecture(java.lang.String architecture) { + this.architecture = architecture; + } + public java.lang.String getArchitecture() { + return this.architecture; + } + + public java.lang.String schemaVersion; + public void setSchemaVersion(java.lang.String schemaVersion) { + this.schemaVersion = schemaVersion; + } + public java.lang.String getSchemaVersion() { + return this.schemaVersion; + } + + public java.lang.String metadataPath; + public void setMetadataPath(java.lang.String metadataPath) { + this.metadataPath = metadataPath; + } + public java.lang.String getMetadataPath() { + return this.metadataPath; + } + + public java.lang.String hostUuid; + public void setHostUuid(java.lang.String hostUuid) { + this.hostUuid = hostUuid; + } + public java.lang.String getHostUuid() { + return this.hostUuid; + } + + public long sizeBytes; + public void setSizeBytes(long sizeBytes) { + this.sizeBytes = sizeBytes; + } + public long getSizeBytes() { + return this.sizeBytes; + } + + public long lastUpdateTime; + public void setLastUpdateTime(long lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } + public long getLastUpdateTime() { + return this.lastUpdateTime; + } + + public boolean incomplete; + public void setIncomplete(boolean incomplete) { + this.incomplete = incomplete; + } + public boolean getIncomplete() { + return this.incomplete; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderAction.java new file mode 100644 index 00000000000..e3d50dc92c0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.keyprovider.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryKeyProviderAction extends QueryAction { + + 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.keyprovider.api.QueryKeyProviderResult 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; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.keyprovider.api.QueryKeyProviderResult value = res.getResult(org.zstack.sdk.keyprovider.api.QueryKeyProviderResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.api.QueryKeyProviderResult() : 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 = "/key-providers"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderResult.java new file mode 100644 index 00000000000..b331fb5348a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/QueryKeyProviderResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.keyprovider.api; + + + +public class QueryKeyProviderResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyFailedResource.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyFailedResource.java new file mode 100644 index 00000000000..293d667dd6a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyFailedResource.java @@ -0,0 +1,39 @@ +package org.zstack.sdk.keyprovider.api; + + + +public class RekeyFailedResource { + + public java.lang.Long keyRefId; + public void setKeyRefId(java.lang.Long keyRefId) { + this.keyRefId = keyRefId; + } + public java.lang.Long getKeyRefId() { + return this.keyRefId; + } + + public java.lang.String resourceType; + public void setResourceType(java.lang.String resourceType) { + this.resourceType = resourceType; + } + public java.lang.String getResourceType() { + return this.resourceType; + } + + public java.lang.String resourceUuid; + public void setResourceUuid(java.lang.String resourceUuid) { + this.resourceUuid = resourceUuid; + } + public java.lang.String getResourceUuid() { + return this.resourceUuid; + } + + public java.lang.String reason; + public void setReason(java.lang.String reason) { + this.reason = reason; + } + public java.lang.String getReason() { + return this.reason; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsAction.java new file mode 100644 index 00000000000..ba08306ec15 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsAction.java @@ -0,0 +1,113 @@ +package org.zstack.sdk.keyprovider.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RekeyKeyProviderRefsAction 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.keyprovider.api.RekeyKeyProviderRefsResult 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 = false, nonempty = true, nullElements = false, emptyString = false, noTrim = false) + public java.util.List refIds; + + @Param(required = false, nonempty = true, nullElements = false, emptyString = false, noTrim = false) + public java.util.List resourceUuids; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String resourceType; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String providerUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public boolean rekeyAll = false; + + @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.keyprovider.api.RekeyKeyProviderRefsResult value = res.getResult(org.zstack.sdk.keyprovider.api.RekeyKeyProviderRefsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.api.RekeyKeyProviderRefsResult() : 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 = "/key-providers/{providerUuid}/rekey"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "rekeyKeyProviderRefs"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsResult.java new file mode 100644 index 00000000000..32a5c938b10 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyKeyProviderRefsResult.java @@ -0,0 +1,46 @@ +package org.zstack.sdk.keyprovider.api; + + + +public class RekeyKeyProviderRefsResult { + public int totalCount; + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + public int getTotalCount() { + return this.totalCount; + } + + public int successCount; + public void setSuccessCount(int successCount) { + this.successCount = successCount; + } + public int getSuccessCount() { + return this.successCount; + } + + public int skippedCount; + public void setSkippedCount(int skippedCount) { + this.skippedCount = skippedCount; + } + public int getSkippedCount() { + return this.skippedCount; + } + + public int failedCount; + public void setFailedCount(int failedCount) { + this.failedCount = failedCount; + } + public int getFailedCount() { + return this.failedCount; + } + + public java.util.List providerResults; + public void setProviderResults(java.util.List providerResults) { + this.providerResults = providerResults; + } + public java.util.List getProviderResults() { + return this.providerResults; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyProviderResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyProviderResult.java new file mode 100644 index 00000000000..bbebfaa0fe0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeyProviderResult.java @@ -0,0 +1,71 @@ +package org.zstack.sdk.keyprovider.api; + + + +public class RekeyProviderResult { + + public java.lang.String providerUuid; + public void setProviderUuid(java.lang.String providerUuid) { + this.providerUuid = providerUuid; + } + public java.lang.String getProviderUuid() { + return this.providerUuid; + } + + public java.lang.String providerName; + public void setProviderName(java.lang.String providerName) { + this.providerName = providerName; + } + public java.lang.String getProviderName() { + return this.providerName; + } + + public int totalRefCount; + public void setTotalRefCount(int totalRefCount) { + this.totalRefCount = totalRefCount; + } + public int getTotalRefCount() { + return this.totalRefCount; + } + + public int successRefCount; + public void setSuccessRefCount(int successRefCount) { + this.successRefCount = successRefCount; + } + public int getSuccessRefCount() { + return this.successRefCount; + } + + public int skippedRefCount; + public void setSkippedRefCount(int skippedRefCount) { + this.skippedRefCount = skippedRefCount; + } + public int getSkippedRefCount() { + return this.skippedRefCount; + } + + public int failedRefCount; + public void setFailedRefCount(int failedRefCount) { + this.failedRefCount = failedRefCount; + } + public int getFailedRefCount() { + return this.failedRefCount; + } + + public java.util.List skippedResources; + public void setSkippedResources(java.util.List skippedResources) { + this.skippedResources = skippedResources; + } + public java.util.List getSkippedResources() { + return this.skippedResources; + } + + public java.util.List failedResources; + public void setFailedResources(java.util.List failedResources) { + this.failedResources = failedResources; + } + public java.util.List getFailedResources() { + return this.failedResources; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeySkippedResource.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeySkippedResource.java new file mode 100644 index 00000000000..70dce8675bf --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/api/RekeySkippedResource.java @@ -0,0 +1,39 @@ +package org.zstack.sdk.keyprovider.api; + + + +public class RekeySkippedResource { + + public java.lang.Long keyRefId; + public void setKeyRefId(java.lang.Long keyRefId) { + this.keyRefId = keyRefId; + } + public java.lang.Long getKeyRefId() { + return this.keyRefId; + } + + public java.lang.String resourceType; + public void setResourceType(java.lang.String resourceType) { + this.resourceType = resourceType; + } + public java.lang.String getResourceType() { + return this.resourceType; + } + + public java.lang.String resourceUuid; + public void setResourceUuid(java.lang.String resourceUuid) { + this.resourceUuid = resourceUuid; + } + public java.lang.String getResourceUuid() { + return this.resourceUuid; + } + + public java.lang.String reason; + public void setReason(java.lang.String reason) { + this.reason = reason; + } + public java.lang.String getReason() { + return this.reason; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsAction.java new file mode 100644 index 00000000000..ba43ed60a34 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsAction.java @@ -0,0 +1,125 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CreateKmsAction 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.keyprovider.kms.api.CreateKmsResult 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, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String endpoint; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, numberRange = {1L,65535L}, noTrim = false) + public java.lang.Integer port; + + @Param(required = false, validValues = {"1.0","1.1","1.2","1.3","1.4","2.0","2.1"}, maxLength = 32, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String kmipVersion = "1.2"; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String username; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String password; + + @Param(required = true, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String name; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @Param(required = false) + public java.lang.String resourceUuid; + + @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.keyprovider.kms.api.CreateKmsResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.CreateKmsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.CreateKmsResult() : 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 = "/key-providers/kms"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsResult.java new file mode 100644 index 00000000000..ae1e24307a1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/CreateKmsResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KeyProviderInventory; + +public class CreateKmsResult { + public KeyProviderInventory inventory; + public void setInventory(KeyProviderInventory inventory) { + this.inventory = inventory; + } + public KeyProviderInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsAction.java new file mode 100644 index 00000000000..e724f5bb544 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class DeleteKmsAction 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.keyprovider.kms.api.DeleteKmsResult 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.lang.String deleteMode = "Permissive"; + + @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.keyprovider.kms.api.DeleteKmsResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.DeleteKmsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.DeleteKmsResult() : 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 = "DELETE"; + info.path = "/key-providers/kms/{uuid}"; + info.needSession = true; + info.needPoll = true; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsResult.java new file mode 100644 index 00000000000..a88e9a3559e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/DeleteKmsResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk.keyprovider.kms.api; + + + +public class DeleteKmsResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsAction.java new file mode 100644 index 00000000000..405d905ffc5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class GetKmsServerCertFromKmsAction 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.keyprovider.kms.api.GetKmsServerCertFromKmsResult 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 = false, 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; + + @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.keyprovider.kms.api.GetKmsServerCertFromKmsResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.GetKmsServerCertFromKmsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.GetKmsServerCertFromKmsResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "getKmsServerCertFromKms"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsResult.java new file mode 100644 index 00000000000..08a835fb0a5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/GetKmsServerCertFromKmsResult.java @@ -0,0 +1,30 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.CertificateInfo; + +public class GetKmsServerCertFromKmsResult { + public java.lang.String serverCertPem; + public void setServerCertPem(java.lang.String serverCertPem) { + this.serverCertPem = serverCertPem; + } + public java.lang.String getServerCertPem() { + return this.serverCertPem; + } + + public boolean selfSigned; + public void setSelfSigned(boolean selfSigned) { + this.selfSigned = selfSigned; + } + public boolean getSelfSigned() { + return this.selfSigned; + } + + public CertificateInfo serverCertInfo; + public void setServerCertInfo(CertificateInfo serverCertInfo) { + this.serverCertInfo = serverCertInfo; + } + public CertificateInfo getServerCertInfo() { + return this.serverCertInfo; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsAction.java new file mode 100644 index 00000000000..184ed6729d0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryKmsAction extends QueryAction { + + 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.keyprovider.kms.api.QueryKmsResult 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; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.keyprovider.kms.api.QueryKmsResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.QueryKmsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.QueryKmsResult() : 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 = "/key-providers/kms"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsResult.java new file mode 100644 index 00000000000..d0d76d2f982 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/QueryKmsResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.keyprovider.kms.api; + + + +public class QueryKmsResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsAction.java new file mode 100644 index 00000000000..566dd7087f0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsAction.java @@ -0,0 +1,119 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UpdateKmsAction 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.keyprovider.kms.api.UpdateKmsResult 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 = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String endpoint; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = false, numberRange = {1L,65535L}, noTrim = false) + public java.lang.Integer port; + + @Param(required = false, validValues = {"1.0","1.1","1.2","1.3","1.4","2.0","2.1"}, maxLength = 32, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String kmipVersion; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String username; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String password; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @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.keyprovider.kms.api.UpdateKmsResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.UpdateKmsResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.UpdateKmsResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "updateKms"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsResult.java new file mode 100644 index 00000000000..8f48d409109 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UpdateKmsResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KeyProviderInventory; + +public class UpdateKmsResult { + public KeyProviderInventory inventory; + public void setInventory(KeyProviderInventory inventory) { + this.inventory = inventory; + } + public KeyProviderInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrAction.java new file mode 100644 index 00000000000..84f2ad9b579 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UploadKmsClientCsrAction 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.keyprovider.kms.api.UploadKmsClientCsrResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String csrPem; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String csrKeyPem; + + @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.keyprovider.kms.api.UploadKmsClientCsrResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.UploadKmsClientCsrResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientCsrResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "uploadKmsClientCsr"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrResult.java new file mode 100644 index 00000000000..562abefd6f9 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientCsrResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KmsIdentityInventory; + +public class UploadKmsClientCsrResult { + public KmsIdentityInventory inventory; + public void setInventory(KmsIdentityInventory inventory) { + this.inventory = inventory; + } + public KmsIdentityInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityAction.java new file mode 100644 index 00000000000..adc46da986f --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityAction.java @@ -0,0 +1,110 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UploadKmsClientIdentityAction 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.keyprovider.kms.api.UploadKmsClientIdentityResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, validValues = {"PLATFORM","UPLOADED","CSR"}, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String identityType; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String kmsClientCertPem; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String kmsClientKeyPem; + + @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.keyprovider.kms.api.UploadKmsClientIdentityResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.UploadKmsClientIdentityResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientIdentityResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "uploadKmsClientIdentity"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityResult.java new file mode 100644 index 00000000000..e71e7a51c1e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientIdentityResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KmsIdentityInventory; + +public class UploadKmsClientIdentityResult { + public KmsIdentityInventory inventory; + public void setInventory(KmsIdentityInventory inventory) { + this.inventory = inventory; + } + public KmsIdentityInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertAction.java new file mode 100644 index 00000000000..eb2a7e94085 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UploadKmsClientSignedCertAction 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.keyprovider.kms.api.UploadKmsClientSignedCertResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String signedClientCertPem; + + @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.keyprovider.kms.api.UploadKmsClientSignedCertResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.UploadKmsClientSignedCertResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientSignedCertResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "uploadKmsClientSignedCert"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertResult.java new file mode 100644 index 00000000000..92ada658c38 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsClientSignedCertResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KmsIdentityInventory; + +public class UploadKmsClientSignedCertResult { + public KmsIdentityInventory inventory; + public void setInventory(KmsIdentityInventory inventory) { + this.inventory = inventory; + } + public KmsIdentityInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertAction.java new file mode 100644 index 00000000000..023bd8d469e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UploadKmsServerCertAction 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.keyprovider.kms.api.UploadKmsServerCertResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String serverCertPem; + + @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.keyprovider.kms.api.UploadKmsServerCertResult value = res.getResult(org.zstack.sdk.keyprovider.kms.api.UploadKmsServerCertResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.kms.api.UploadKmsServerCertResult() : 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 = "/key-providers/kms/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "uploadKmsServerCert"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertResult.java new file mode 100644 index 00000000000..7a0101c99c4 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/kms/api/UploadKmsServerCertResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.kms.api; + +import org.zstack.sdk.KmsInventory; + +public class UploadKmsServerCertResult { + public KmsInventory inventory; + public void setInventory(KmsInventory inventory) { + this.inventory = inventory; + } + public KmsInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpAction.java new file mode 100644 index 00000000000..e4e996818c1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class BackupNkpAction 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.keyprovider.nkp.api.BackupNkpResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String password; + + @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.keyprovider.nkp.api.BackupNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.BackupNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.BackupNkpResult() : 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 = "/key-providers/nkp/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "backupNkp"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpResult.java new file mode 100644 index 00000000000..a8880061e50 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/BackupNkpResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.nkp.api; + + + +public class BackupNkpResult { + public java.lang.String content; + public void setContent(java.lang.String content) { + this.content = content; + } + public java.lang.String getContent() { + return this.content; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpAction.java new file mode 100644 index 00000000000..15a3642d5c0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpAction.java @@ -0,0 +1,116 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CreateNkpAction 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.keyprovider.nkp.api.CreateNkpResult 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 = false, validValues = {"HKDF-SHA256"}, maxLength = 64, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String kdf = "HKDF-SHA256"; + + @Param(required = false, validValues = {"providerName"}, maxLength = 64, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String saltPolicy = "providerName"; + + @Param(required = true, maxLength = 255, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String name; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @Param(required = false) + public java.lang.String resourceUuid; + + @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.keyprovider.nkp.api.CreateNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.CreateNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.CreateNkpResult() : 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 = "/key-providers/nkp"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpResult.java new file mode 100644 index 00000000000..e00c3112235 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/CreateNkpResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import org.zstack.sdk.KeyProviderInventory; + +public class CreateNkpResult { + public KeyProviderInventory inventory; + public void setInventory(KeyProviderInventory inventory) { + this.inventory = inventory; + } + public KeyProviderInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpAction.java new file mode 100644 index 00000000000..7e7b433eaf6 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class DeleteNkpAction 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.keyprovider.nkp.api.DeleteNkpResult 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.lang.String deleteMode = "Permissive"; + + @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.keyprovider.nkp.api.DeleteNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.DeleteNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.DeleteNkpResult() : 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 = "DELETE"; + info.path = "/key-providers/nkp/{uuid}"; + info.needSession = true; + info.needPoll = true; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpResult.java new file mode 100644 index 00000000000..3dbd48e495b --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/DeleteNkpResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk.keyprovider.nkp.api; + + + +public class DeleteNkpResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreAction.java new file mode 100644 index 00000000000..db7265e783f --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ParseNkpRestoreAction 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.keyprovider.nkp.api.ParseNkpRestoreResult 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 = false, noTrim = false) + public java.lang.String contentBase64; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String password; + + @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.keyprovider.nkp.api.ParseNkpRestoreResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.ParseNkpRestoreResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.ParseNkpRestoreResult() : 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 = "/key-providers/nkp/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "parseNkpRestore"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreResult.java new file mode 100644 index 00000000000..5b5ef838c90 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/ParseNkpRestoreResult.java @@ -0,0 +1,30 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import org.zstack.sdk.NkpRestoreInfo; + +public class ParseNkpRestoreResult { + public NkpRestoreInfo restoreInfo; + public void setRestoreInfo(NkpRestoreInfo restoreInfo) { + this.restoreInfo = restoreInfo; + } + public NkpRestoreInfo getRestoreInfo() { + return this.restoreInfo; + } + + public java.lang.String code; + public void setCode(java.lang.String code) { + this.code = code; + } + public java.lang.String getCode() { + return this.code; + } + + public java.lang.String reason; + public void setReason(java.lang.String reason) { + this.reason = reason; + } + public java.lang.String getReason() { + return this.reason; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpAction.java new file mode 100644 index 00000000000..6eb869c7821 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryNkpAction extends QueryAction { + + 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.keyprovider.nkp.api.QueryNkpResult 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; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.keyprovider.nkp.api.QueryNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.QueryNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.QueryNkpResult() : 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 = "/key-providers/nkp"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpResult.java new file mode 100644 index 00000000000..3a1b8f44abd --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/QueryNkpResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.keyprovider.nkp.api; + + + +public class QueryNkpResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpAction.java new file mode 100644 index 00000000000..f7c44403fe8 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RestoreNkpAction 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.keyprovider.nkp.api.RestoreNkpResult 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 = false, noTrim = false) + public java.lang.String contentBase64; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String password; + + @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.keyprovider.nkp.api.RestoreNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.RestoreNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.RestoreNkpResult() : 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 = "/key-providers/nkp/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "restoreNkp"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpResult.java new file mode 100644 index 00000000000..812f84e8fa9 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/RestoreNkpResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import org.zstack.sdk.NkpInventory; + +public class RestoreNkpResult { + public NkpInventory inventory; + public void setInventory(NkpInventory inventory) { + this.inventory = inventory; + } + public NkpInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpAction.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpAction.java new file mode 100644 index 00000000000..3020624f823 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UpdateNkpAction 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.keyprovider.nkp.api.UpdateNkpResult 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 = false, noTrim = false) + public java.lang.String uuid; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @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.keyprovider.nkp.api.UpdateNkpResult value = res.getResult(org.zstack.sdk.keyprovider.nkp.api.UpdateNkpResult.class); + ret.value = value == null ? new org.zstack.sdk.keyprovider.nkp.api.UpdateNkpResult() : 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 = "/key-providers/nkp/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "updateNkp"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpResult.java b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpResult.java new file mode 100644 index 00000000000..b7302e2c291 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/keyprovider/nkp/api/UpdateNkpResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.keyprovider.nkp.api; + +import org.zstack.sdk.KeyProviderInventory; + +public class UpdateNkpResult { + public KeyProviderInventory inventory; + public void setInventory(KeyProviderInventory inventory) { + this.inventory = inventory; + } + public KeyProviderInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmAction.java new file mode 100644 index 00000000000..63fdcb78673 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmAction.java @@ -0,0 +1,110 @@ +package org.zstack.sdk.tpm.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class AddTpmAction 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.tpm.api.AddTpmResult 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 = false, maxLength = 32, minLength = 32, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String keyProviderUuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String vmInstanceUuid; + + @Param(required = false) + public java.lang.String resourceUuid; + + @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.tpm.api.AddTpmResult value = res.getResult(org.zstack.sdk.tpm.api.AddTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.api.AddTpmResult() : 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 = "/tpms"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmResult.java new file mode 100644 index 00000000000..05effc20779 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/AddTpmResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm.api; + +import org.zstack.sdk.tpm.entity.TpmInventory; + +public class AddTpmResult { + public TpmInventory inventory; + public void setInventory(TpmInventory inventory) { + this.inventory = inventory; + } + public TpmInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityAction.java new file mode 100644 index 00000000000..d4a8c793040 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityAction.java @@ -0,0 +1,98 @@ +package org.zstack.sdk.tpm.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class GetTpmCapabilityAction 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.tpm.api.GetTpmCapabilityResult 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 = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String tpmUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String vmInstanceUuid; + + @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.tpm.api.GetTpmCapabilityResult value = res.getResult(org.zstack.sdk.tpm.api.GetTpmCapabilityResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.api.GetTpmCapabilityResult() : 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 = "/tpms/capability"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityResult.java new file mode 100644 index 00000000000..66d43f8a70d --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/GetTpmCapabilityResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm.api; + +import org.zstack.sdk.tpm.entity.TpmCapabilityView; + +public class GetTpmCapabilityResult { + public TpmCapabilityView inventory; + public void setInventory(TpmCapabilityView inventory) { + this.inventory = inventory; + } + public TpmCapabilityView getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmAction.java new file mode 100644 index 00000000000..e6b41993ebb --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.tpm.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryTpmAction extends QueryAction { + + 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.tpm.api.QueryTpmResult 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; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.tpm.api.QueryTpmResult value = res.getResult(org.zstack.sdk.tpm.api.QueryTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.api.QueryTpmResult() : 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 = "/tpms"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmResult.java new file mode 100644 index 00000000000..1414409e00a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/QueryTpmResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.tpm.api; + + + +public class QueryTpmResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmAction.java new file mode 100644 index 00000000000..a404cfe8df3 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk.tpm.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RemoveTpmAction 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.tpm.api.RemoveTpmResult 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 = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String tpmUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String vmInstanceUuid; + + @Param(required = false) + public java.lang.String deleteMode = "Permissive"; + + @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.tpm.api.RemoveTpmResult value = res.getResult(org.zstack.sdk.tpm.api.RemoveTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.api.RemoveTpmResult() : 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 = "DELETE"; + info.path = "/tpms"; + info.needSession = true; + info.needPoll = true; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmResult.java new file mode 100644 index 00000000000..93f74261a83 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/RemoveTpmResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk.tpm.api; + + + +public class RemoveTpmResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmAction.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmAction.java new file mode 100644 index 00000000000..28fd0257b14 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk.tpm.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UpdateTpmAction 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.tpm.api.UpdateTpmResult 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 = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String vmInstanceUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String tpmUuid; + + @Param(required = false, maxLength = 32, minLength = 32, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String keyProviderUuid; + + @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.tpm.api.UpdateTpmResult value = res.getResult(org.zstack.sdk.tpm.api.UpdateTpmResult.class); + ret.value = value == null ? new org.zstack.sdk.tpm.api.UpdateTpmResult() : 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 = "/tpms"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "updateTpm"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmResult.java b/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmResult.java new file mode 100644 index 00000000000..1157fd732cb --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/api/UpdateTpmResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.tpm.api; + +import org.zstack.sdk.tpm.entity.TpmInventory; + +public class UpdateTpmResult { + public TpmInventory inventory; + public void setInventory(TpmInventory inventory) { + this.inventory = inventory; + } + public TpmInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmCapabilityView.java b/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmCapabilityView.java new file mode 100644 index 00000000000..38fa336e3d5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmCapabilityView.java @@ -0,0 +1,79 @@ +package org.zstack.sdk.tpm.entity; + + + +public class TpmCapabilityView { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String vmInstanceUuid; + public void setVmInstanceUuid(java.lang.String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + public java.lang.String getVmInstanceUuid() { + return this.vmInstanceUuid; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + + public java.util.List fileRefs; + public void setFileRefs(java.util.List fileRefs) { + this.fileRefs = fileRefs; + } + public java.util.List getFileRefs() { + return this.fileRefs; + } + + public java.lang.String edkVersion; + public void setEdkVersion(java.lang.String edkVersion) { + this.edkVersion = edkVersion; + } + public java.lang.String getEdkVersion() { + return this.edkVersion; + } + + public java.lang.String swtpmVersion; + public void setSwtpmVersion(java.lang.String swtpmVersion) { + this.swtpmVersion = swtpmVersion; + } + public java.lang.String getSwtpmVersion() { + return this.swtpmVersion; + } + + public boolean resetTpmAfterVmCloneConfig; + public void setResetTpmAfterVmCloneConfig(boolean resetTpmAfterVmCloneConfig) { + this.resetTpmAfterVmCloneConfig = resetTpmAfterVmCloneConfig; + } + public boolean getResetTpmAfterVmCloneConfig() { + return this.resetTpmAfterVmCloneConfig; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmInventory.java b/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmInventory.java new file mode 100644 index 00000000000..538b12182ca --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/tpm/entity/TpmInventory.java @@ -0,0 +1,47 @@ +package org.zstack.sdk.tpm.entity; + + + +public class TpmInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String vmInstanceUuid; + public void setVmInstanceUuid(java.lang.String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + public java.lang.String getVmInstanceUuid() { + return this.vmInstanceUuid; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java b/sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java new file mode 100644 index 00000000000..555cf4b8d45 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/vm/entity/VmHostFileInventory.java @@ -0,0 +1,87 @@ +package org.zstack.sdk.vm.entity; + + + +public class VmHostFileInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String vmInstanceUuid; + public void setVmInstanceUuid(java.lang.String vmInstanceUuid) { + this.vmInstanceUuid = vmInstanceUuid; + } + public java.lang.String getVmInstanceUuid() { + return this.vmInstanceUuid; + } + + public java.lang.String hostUuid; + public void setHostUuid(java.lang.String hostUuid) { + this.hostUuid = hostUuid; + } + public java.lang.String getHostUuid() { + return this.hostUuid; + } + + public java.lang.String type; + public void setType(java.lang.String type) { + this.type = type; + } + public java.lang.String getType() { + return this.type; + } + + public java.lang.String path; + public void setPath(java.lang.String path) { + this.path = path; + } + public java.lang.String getPath() { + return this.path; + } + + public java.lang.String lastSyncReason; + public void setLastSyncReason(java.lang.String lastSyncReason) { + this.lastSyncReason = lastSyncReason; + } + public java.lang.String getLastSyncReason() { + return this.lastSyncReason; + } + + public java.sql.Timestamp changeDate; + public void setChangeDate(java.sql.Timestamp changeDate) { + this.changeDate = changeDate; + } + public java.sql.Timestamp getChangeDate() { + return this.changeDate; + } + + public java.sql.Timestamp lastSyncDate; + public void setLastSyncDate(java.sql.Timestamp lastSyncDate) { + this.lastSyncDate = lastSyncDate; + } + public java.sql.Timestamp getLastSyncDate() { + return this.lastSyncDate; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} 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..1aa4922a99e 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; @@ -318,6 +320,9 @@ private void checkPrimaryStatus(Message msg) { new PrimaryStorageValidater().disable().maintenance() .validate(); } + } else if (msg instanceof RebaseVolumeBackingFileOnPrimaryStorageMsg) { + new PrimaryStorageValidater().maintenance() + .validate(); } } @@ -417,6 +422,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,11 +950,29 @@ protected void handleApiMessage(APIMessage msg) { handle((APICleanUpStorageTrashOnPrimaryStorageMsg) msg); } else if (msg instanceof APIAddStorageProtocolMsg) { handle((APIAddStorageProtocolMsg) msg); + } else if (msg instanceof APITakeoverPrimaryStorageMsg) { + handle((APITakeoverPrimaryStorageMsg) msg); + } else if (msg instanceof APICheckPrimaryStorageConsistencyMsg) { + handle((APICheckPrimaryStorageConsistencyMsg) msg); + } else if (msg instanceof APIScanVmInstanceMetadataFromPrimaryStorageMsg) { + handle((APIScanVmInstanceMetadataFromPrimaryStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } + protected void handle(APITakeoverPrimaryStorageMsg msg) { + APITakeoverPrimaryStorageEvent event = new APITakeoverPrimaryStorageEvent(msg.getId()); + event.setError(operr("takeover not supported for primary storage type[%s]", self.getType())); + bus.publish(event); + } + + protected void handle(APICheckPrimaryStorageConsistencyMsg msg) { + APICheckPrimaryStorageConsistencyReply reply = new APICheckPrimaryStorageConsistencyReply(); + reply.setError(operr("consistency check not supported for primary storage type[%s]", self.getType())); + bus.reply(msg, reply); + } + private void handle(APIAddStorageProtocolMsg msg) { APIAddStorageProtocolEvent evt = new APIAddStorageProtocolEvent(msg.getId()); addStorageProtocol(msg, new Completion(msg) { @@ -1773,6 +1806,36 @@ protected void handle(UnlinkBitsOnPrimaryStorageMsg msg) { bus.reply(msg, reply); }; + protected void handle(UpdateVmInstanceMetadataOnPrimaryStorageMsg msg) { + UpdateVmInstanceMetadataOnPrimaryStorageReply reply = new UpdateVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("operation not supported")); + bus.reply(msg, reply); + } + + protected void handle(GetVmInstanceMetadataFromPrimaryStorageMsg msg) { + GetVmInstanceMetadataFromPrimaryStorageReply reply = new GetVmInstanceMetadataFromPrimaryStorageReply(); + reply.setError(operr("operation not supported")); + bus.reply(msg, reply); + } + + protected void handle(ScanVmInstanceMetadataFromPrimaryStorageMsg msg) { + ScanVmInstanceMetadataFromPrimaryStorageReply reply = new ScanVmInstanceMetadataFromPrimaryStorageReply(); + reply.setError(operr("operation not supported")); + bus.reply(msg, reply); + } + + protected void handle(CleanupVmInstanceMetadataOnPrimaryStorageMsg msg) { + CleanupVmInstanceMetadataOnPrimaryStorageReply reply = new CleanupVmInstanceMetadataOnPrimaryStorageReply(); + reply.setError(operr("operation not supported")); + bus.reply(msg, reply); + } + + protected void handle(RebaseVolumeBackingFileOnPrimaryStorageMsg msg) { + RebaseVolumeBackingFileOnPrimaryStorageReply reply = new RebaseVolumeBackingFileOnPrimaryStorageReply(); + reply.setError(operr("operation not supported")); + bus.reply(msg, reply); + } + // don't attach any cluster public boolean isUnmounted() { long count = Q.New(PrimaryStorageClusterRefVO.class) @@ -1812,4 +1875,56 @@ 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(); + + if (self.getStatus() != PrimaryStorageStatus.Connected) { + reply.setError(Platform.operr("primary storage[uuid:%s] is not Connected (status=%s), cannot scan metadata", + self.getUuid(), self.getStatus())); + bus.reply(msg, reply); + return; + } + + String psType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, msg.getPrimaryStorageUuid()).findValue(); + if (psType == null) { + reply.setError(Platform.operr("primary storage[uuid:%s] not found", msg.getPrimaryStorageUuid())); + bus.reply(msg, reply); + return; + } + 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 metadata = re.getVmInstanceMetadata(); + if (metadata == null) { + metadata = Collections.emptyList(); + } + List filtered = metadata.stream() + .filter(Objects::nonNull) + .filter(e -> e.getVmCategory() != null) + .filter(e -> !VmMetadataCategory.VM_TEMPLATE_CACHE.name().equals(e.getVmCategory())) + .collect(Collectors.toList()); + reply.setVmInstanceMetadata(filtered); + bus.reply(msg, reply); + } + }); + } } diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStoragePathMaker.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStoragePathMaker.java index 7ef4b4dcd24..2458ee91d60 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStoragePathMaker.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStoragePathMaker.java @@ -43,6 +43,10 @@ public static String makeDataVolumeInstallPath(String volUuid) { return PathUtil.join("dataVolumes", "acct-" + getAccountUuidOfResource(volUuid), "vol-" + volUuid, volUuid + ".qcow2"); } + public static String makeNvRamVolumeInstallPath(String volUuid) { + return PathUtil.join("nvRam", "acct-" + getAccountUuidOfResource(volUuid), "vol-" + volUuid, volUuid + ".raw"); + } + public static String makeImageFromSnapshotWorkspacePath(String imageUuid) { return PathUtil.join("snapshotWorkspace", String.format("image-%s", imageUuid)); } diff --git a/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotApiInterceptor.java b/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotApiInterceptor.java index 1da009fc2d9..417fdb4c390 100755 --- a/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotApiInterceptor.java +++ b/storage/src/main/java/org/zstack/storage/snapshot/VolumeSnapshotApiInterceptor.java @@ -5,7 +5,6 @@ import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; -import org.zstack.core.db.SQL; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.core.errorcode.ErrorFacade; @@ -16,7 +15,6 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.storage.snapshot.*; import org.zstack.header.storage.snapshot.group.*; -import org.zstack.header.vm.VmInstanceInventory; import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmInstanceVO; import org.zstack.header.vm.VmInstanceVO_; @@ -29,7 +27,6 @@ import static org.zstack.storage.snapshot.VolumeSnapshotMessageRouter.getResourceIdToRouteMsg; import javax.persistence.Tuple; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -113,13 +110,18 @@ private void validate(APIRevertVmFromSnapshotGroupMsg msg) { } } - if (isWithMemoryForSnapshotGroup(group) - && Q.New(VmInstanceVO.class) - .eq(VmInstanceVO_.uuid, group.getVmInstanceUuid()) - .in(VmInstanceVO_.state, Arrays.asList(VmInstanceState.Running, VmInstanceState.Paused)) - .isExists()) { - throw new ApiMessageInterceptionException(argerr("Can not take memory snapshot, expected vm states are [%s, %s]", - VmInstanceState.Running.toString(), VmInstanceState.Paused.toString())); + if (isWithMemoryForSnapshotGroup(group)) { + VmInstanceState vmState = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.state) + .eq(VmInstanceVO_.uuid, group.getVmInstanceUuid()) + .findValue(); + if (!VolumeSnapshotConstant.ALLOW_TAKE_MEMORY_SNAPSHOTS_VM_STATES.contains(vmState)) { + throw new ApiMessageInterceptionException(argerr( + "Can not revert VM with memory snapshot: unexpected VM state") + .withOpaque("vm.uuid", group.getVmInstanceUuid()) + .withOpaque("vm.state", vmState.toString()) + .withOpaque("expect.states", VolumeSnapshotConstant.ALLOW_TAKE_MEMORY_SNAPSHOTS_VM_STATES)); + } } msg.setVmInstanceUuid(group.getVmInstanceUuid()); diff --git a/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java b/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java index dbc130475e5..294f3e39f33 100644 --- a/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java +++ b/storage/src/main/java/org/zstack/storage/snapshot/group/VolumeSnapshotGroupBase.java @@ -29,13 +29,18 @@ import org.zstack.header.storage.snapshot.VolumeSnapshotConstant; import org.zstack.header.storage.snapshot.VolumeSnapshotVO; import org.zstack.header.storage.snapshot.group.*; +import org.zstack.header.vm.additions.RestoreVmHostFileMsg; import org.zstack.header.vm.RestoreVmInstanceMsg; import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; +import org.zstack.header.vm.additions.VmHostFileSyncReason; import org.zstack.header.vm.devices.VmInstanceResourceMetadataManager; import org.zstack.header.volume.VolumeType; import org.zstack.header.volume.VolumeVO; import org.zstack.header.volume.VolumeVO_; import org.zstack.storage.snapshot.VolumeSnapshotGlobalConfig; +import org.zstack.utils.DebugUtils; import org.zstack.utils.TimeUtils; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -142,6 +147,9 @@ public String getSyncSignature() { public void run(SyncTaskChain chain) { APIUngroupVolumeSnapshotGroupEvent evt = new APIUngroupVolumeSnapshotGroupEvent(msg.getId()); dbf.remove(self); + SQL.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, msg.getUuid()) + .delete(); bus.publish(evt); chain.next(); } @@ -209,28 +217,43 @@ private void handle(DeleteVolumeSnapshotGroupInnerMsg msg) { logger.debug(String.format("skip snapshots not belong to origin vm[uuid:%s]", self.getVmInstanceUuid())); } - new While<>(snapshots).all((snapshot, compl) -> { - DeleteVolumeSnapshotMsg rmsg = new DeleteVolumeSnapshotMsg(); - rmsg.setSnapshotUuid(snapshot.getUuid()); - rmsg.setVolumeUuid(snapshot.getVolumeUuid()); - rmsg.setTreeUuid(snapshot.getTreeUuid()); - rmsg.setDeletionMode(msg.getDeletionMode()); - rmsg.setScope(msg.getScope()); - rmsg.setDirection(msg.getDirection()); - bus.makeTargetServiceIdByResourceUuid(rmsg, VolumeSnapshotConstant.SERVICE_ID, getResourceIdToRouteMsg(snapshot)); - bus.send(rmsg, new CloudBusCallBack(compl) { - @Override - public void run(MessageReply r) { - reply.addResult(new DeleteSnapshotGroupResult(rmsg.getSnapshotUuid(), rmsg.getVolumeUuid(), r.getError())); - compl.done(); - } - }); - }).run(new WhileDoneCompletion(msg) { - @Override - public void done(ErrorCodeList errorCodeList) { + SimpleFlowChain.of("delete-volume-snapshot-group") + .then("delete-volume-snapshots", (trigger) -> + new While<>(snapshots).step((snapshot, compl) -> { + DeleteVolumeSnapshotMsg rmsg = new DeleteVolumeSnapshotMsg(); + rmsg.setSnapshotUuid(snapshot.getUuid()); + rmsg.setVolumeUuid(snapshot.getVolumeUuid()); + rmsg.setTreeUuid(snapshot.getTreeUuid()); + rmsg.setDeletionMode(msg.getDeletionMode()); + rmsg.setScope(msg.getScope()); + rmsg.setDirection(msg.getDirection()); + bus.makeTargetServiceIdByResourceUuid(rmsg, VolumeSnapshotConstant.SERVICE_ID, getResourceIdToRouteMsg(snapshot)); + bus.send(rmsg, new CloudBusCallBack(compl) { + @Override + public void run(MessageReply r) { + reply.addResult(new DeleteSnapshotGroupResult(rmsg.getSnapshotUuid(), rmsg.getVolumeUuid(), r.getError())); + compl.done(); + } + }); + }, 5).run(new WhileDoneCompletion(msg) { + @Override + public void done(ErrorCodeList errorCodeList) { + trigger.next(); + } + })) + .then("delete-vm-host-backup-files", trigger -> { + SQL.New(VmHostBackupFileVO.class) + .eq(VmHostBackupFileVO_.resourceUuid, self.getUuid()) + .delete(); + trigger.next(); + }) + .propagateExceptionTo(msg) + .done(() -> bus.reply(msg, reply)) + .error(errorCode -> { + reply.setError(errorCode); bus.reply(msg, reply); - } - }); + }) + .start(); } private void handle(APIRevertVmFromSnapshotGroupMsg msg) { @@ -358,46 +381,85 @@ private void handle(RevertVmFromSnapshotGroupInnerMsg msg) { return; } - VolumeSnapshotGroupVO newGroup = null; - if (VolumeSnapshotGlobalConfig.SNAPSHOT_BEFORE_REVERTVOLUME.value(Boolean.class)) { - newGroup = new VolumeSnapshotGroupVO(); - newGroup.setUuid(Platform.getUuid()); - newGroup.setName(String.format("revert-vm-point-%s-%s", vmUuid, TimeUtils.getCurrentTimeStamp("yyyyMMddHHmmss"))); - newGroup.setDescription(String.format("save snapshot for revert vm [uuid:%s]", vmUuid)); - newGroup.setSnapshotCount(snapshots.size()); - newGroup.setVmInstanceUuid(vmUuid); - newGroup.setAccountUuid(msg.getSession().getAccountUuid()); - dbf.persist(newGroup); + class Context { + VolumeSnapshotGroupVO newGroup; } + Context context = new Context(); + + SimpleFlowChain.of("revert-vm-from-snapshot-group-inner") + .then(Flow.of("persist-before-revert-snapshot-in-db") + .runIf(data -> VolumeSnapshotGlobalConfig.SNAPSHOT_BEFORE_REVERTVOLUME.value(Boolean.class)) + .handle(trigger -> { + context.newGroup = new VolumeSnapshotGroupVO(); + context.newGroup.setUuid(Platform.getUuid()); + context.newGroup.setName(String.format("revert-vm-point-%s-%s", vmUuid, TimeUtils.getCurrentTimeStamp("yyyyMMddHHmmss"))); + context.newGroup.setDescription(String.format("save snapshot for revert vm [uuid:%s]", vmUuid)); + context.newGroup.setSnapshotCount(snapshots.size()); + context.newGroup.setVmInstanceUuid(vmUuid); + context.newGroup.setAccountUuid(msg.getSession().getAccountUuid()); + dbf.persist(context.newGroup); + trigger.next(); + }) + .build()) + .then(Flow.of("restore-vm-host-file") + .handle(trigger -> { + RestoreVmHostFileMsg restoreMsg = new RestoreVmHostFileMsg(); + restoreMsg.setVmInstanceUuid(vmUuid); + restoreMsg.setSnapshotGroupUuid(self.getUuid()); + restoreMsg.setSyncReason(VmHostFileSyncReason.RevertSnapshot.reason()); + bus.makeLocalServiceId(restoreMsg, VmInstanceConstant.SECURE_BOOT_SERVICE_ID); + bus.send(restoreMsg, new CloudBusCallBack(trigger) { + @Override + public void run(MessageReply reply) { + if (reply.isSuccess()) { + trigger.next(); + } else { + trigger.fail(reply.getError()); + } + } + }); + }) + .build()) + .then(Flow.of("revert-every-volumes") + .handle(trigger -> { + final String finalNewGroupUuid = context.newGroup == null ? null : context.newGroup.getUuid(); + new While<>(snapshots).each((snapshot, compl) -> { + if (Q.New(VolumeVO.class).eq(VolumeVO_.uuid, snapshot.getVolumeUuid()).eq(VolumeVO_.type, VolumeType.Memory).isExists()) { + compl.done(); + return; + } - final String finalNewGroupUuid = newGroup == null ? null : newGroup.getUuid(); - new While<>(snapshots).each((snapshot, compl) -> { - if (Q.New(VolumeVO.class).eq(VolumeVO_.uuid, snapshot.getVolumeUuid()).eq(VolumeVO_.type, VolumeType.Memory).isExists()) { - compl.done(); - return; - } - - RevertVolumeFromSnapshotGroupMsg rmsg = new RevertVolumeFromSnapshotGroupMsg(); - rmsg.setSnapshotUuid(snapshot.getUuid()); - rmsg.setVolumeUuid(snapshot.getVolumeUuid()); - rmsg.setTreeUuid(snapshot.getTreeUuid()); - rmsg.setSession(msg.getSession()); - rmsg.setNewSnapshotGroupUuid(finalNewGroupUuid); - - bus.makeTargetServiceIdByResourceUuid(rmsg, VolumeSnapshotConstant.SERVICE_ID, getResourceIdToRouteMsg(snapshot)); - bus.send(rmsg, new CloudBusCallBack(compl) { - @Override - public void run(MessageReply r) { - reply.addResult(new RevertSnapshotGroupResult(rmsg.getSnapshotUuid(), rmsg.getVolumeUuid(), r.getError())); - compl.done(); - } - }); - }).run(new WhileDoneCompletion(msg) { - @Override - public void done(ErrorCodeList errorCodeList) { + RevertVolumeFromSnapshotGroupMsg rmsg = new RevertVolumeFromSnapshotGroupMsg(); + rmsg.setSnapshotUuid(snapshot.getUuid()); + rmsg.setVolumeUuid(snapshot.getVolumeUuid()); + rmsg.setTreeUuid(snapshot.getTreeUuid()); + rmsg.setSession(msg.getSession()); + rmsg.setNewSnapshotGroupUuid(finalNewGroupUuid); + + bus.makeTargetServiceIdByResourceUuid(rmsg, VolumeSnapshotConstant.SERVICE_ID, getResourceIdToRouteMsg(snapshot)); + bus.send(rmsg, new CloudBusCallBack(compl) { + @Override + public void run(MessageReply r) { + reply.addResult(new RevertSnapshotGroupResult(rmsg.getSnapshotUuid(), rmsg.getVolumeUuid(), r.getError())); + compl.done(); + } + }); + }).run(new WhileDoneCompletion(msg) { + @Override + public void done(ErrorCodeList errorCodeList) { + DebugUtils.Assert(!errorCodeList.hasError(), "no errorCode expected"); + trigger.next(); + } + }); + }) + .build()) + .propagateExceptionTo(msg) + .done(() -> bus.reply(msg, reply)) + .error(errorCode -> { + reply.setError(errorCode); bus.reply(msg, reply); - } - }); + }) + .start(); } public List getSnapshots() { diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java index 272d75d7a09..f0cd6d4a511 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java @@ -41,6 +41,8 @@ import org.zstack.header.tag.SystemTagVO; import org.zstack.header.tag.SystemTagVO_; import org.zstack.header.vm.*; +import org.zstack.header.vm.additions.VmHostBackupFileVO; +import org.zstack.header.vm.additions.VmHostBackupFileVO_; import org.zstack.header.vm.devices.VmInstanceResourceMetadataManager; import org.zstack.header.volume.*; import org.zstack.header.volume.VolumeConstant.Capability; @@ -62,6 +64,7 @@ import javax.persistence.TypedQuery; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static org.zstack.core.Platform.*; @@ -551,7 +554,8 @@ public void fail(ErrorCode errorCode) { } else if (msg instanceof InstantiateMemoryVolumeMsg) { instantiateMemoryVolume(msg, trigger); } else { - instantiateDataVolume(msg, trigger); + // include: data volume, NvRam, TpmState + instantiateOtherVolume(msg, trigger); } } } @@ -593,7 +597,7 @@ private void instantiateRootVolume(InstantiateRootVolumeMsg msg, FlowTrigger tri doInstantiateVolume(imsg, trigger); } - private void instantiateDataVolume(InstantiateVolumeMsg msg, FlowTrigger trigger) { + private void instantiateOtherVolume(InstantiateVolumeMsg msg, FlowTrigger trigger) { InstantiateVolumeOnPrimaryStorageMsg imsg = new InstantiateVolumeOnPrimaryStorageMsg(); prepareMsg(msg, imsg); doInstantiateVolume(imsg, trigger); @@ -3083,6 +3087,7 @@ private void createSnapshotGroup(CreateVolumeSnapshotGroupMessage msg, ReturnVal CreateVolumesSnapshotMsg cmsg = new CreateVolumesSnapshotMsg(); List volumesSnapshotsJobs = new ArrayList<>(); cmsg.setAccountUuid(msg.getSession().getAccountUuid()); + cmsg.setBackupHostFileIfNeeded(true); VmInstanceInventory vm = msg.getVmInstance(); Map vols = vm.getAllVolumes().stream() @@ -3103,55 +3108,92 @@ private void createSnapshotGroup(CreateVolumeSnapshotGroupMessage msg, ReturnVal cmsg.setVolumeSnapshotJobs(volumesSnapshotsJobs); cmsg.setConsistentType(msg.getConsistentType()); - bus.makeTargetServiceIdByResourceUuid(cmsg, VolumeConstant.SERVICE_ID, msg.getRootVolumeUuid()); - bus.send(cmsg, new CloudBusCallBack(completion) { - @Override - public void run(MessageReply reply) { - if (!reply.isSuccess()) { - completion.fail(reply.getError()); - return; - } + List inventories = new ArrayList<>(); + List hostBackupFileUuidList = new ArrayList<>(); + AtomicReference groupRef = new AtomicReference<>(null); + String resourceUuid = msg.getResourceUuid() == null ? getUuid() : msg.getResourceUuid(); + SimpleFlowChain.of("create-snapshot-group") + .then("handle-create-snapshot-group-message", trigger -> { + bus.makeTargetServiceIdByResourceUuid(cmsg, VolumeConstant.SERVICE_ID, msg.getRootVolumeUuid()); + bus.send(cmsg, new CloudBusCallBack(completion) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + trigger.fail(reply.getError()); + return; + } + CreateVolumesSnapshotReply r = reply.castReply(); + inventories.addAll(r.getInventories()); + if (!CollectionUtils.isEmpty(r.getHostBackupFileUuidList())) { + hostBackupFileUuidList.addAll(r.getHostBackupFileUuidList()); + } + trigger.next(); + } + }); + }) + .then(Flow.of("persist-snapshot-group") + .handle(trigger -> { + List refs = new ArrayList<>(); + VolumeSnapshotGroupVO group = new VolumeSnapshotGroupVO(); + group.setUuid(resourceUuid); + group.setSnapshotCount(cmsg.getVolumeSnapshotJobs().size()); + group.setName(msg.getName()); + group.setDescription(msg.getDescription()); + group.setVmInstanceUuid(vm.getUuid()); + group.setAccountUuid(msg.getSession().getAccountUuid()); + for (VolumeSnapshotInventory inv : inventories) { + VolumeSnapshotGroupRefVO ref = new VolumeSnapshotGroupRefVO(); + ref.setVolumeUuid(inv.getVolumeUuid()); + ref.setVolumeName(vols.get(inv.getVolumeUuid()).getName()); + ref.setVolumeType(inv.getVolumeType()); + ref.setVolumeSnapshotGroupUuid(group.getUuid()); + ref.setVolumeSnapshotUuid(inv.getUuid()); + ref.setVolumeSnapshotName(inv.getName()); + ref.setVolumeSnapshotInstallPath(inv.getPrimaryStorageInstallPath()); + ref.setDeviceId(vols.get(inv.getVolumeUuid()).getDeviceId()); + ref.setVolumeLastAttachDate(vols.get(inv.getVolumeUuid()).getLastAttachDate()); + refs.add(ref); + } + + new SQLBatch() { + @Override + protected void scripts() { + databaseFacade.persist(group); + databaseFacade.persistCollection(refs); + } + }.execute(); - CreateVolumesSnapshotReply r = reply.castReply(); - VolumeSnapshotGroupVO group = createGroup(r); + groupRef.set(group); + trigger.next(); + }) + .rollback(trigger -> { + SQL.New(VolumeSnapshotGroupVO.class) + .eq(VolumeSnapshotGroupVO_.uuid, resourceUuid) + .delete(); + // VolumeSnapshotGroupRefVO delete in cascade + trigger.rollback(); + }) + .build()) + .then(Flow.of("persist-vm-host-backup-file") + .handle(trigger -> { + if (!CollectionUtils.isEmpty(hostBackupFileUuidList)) { + SQL.New(VmHostBackupFileVO.class) + .in(VmHostBackupFileVO_.uuid, hostBackupFileUuidList) + .set(VmHostBackupFileVO_.resourceUuid, resourceUuid) + .update(); + } + trigger.next(); + }) + .build()) + .propagateExceptionTo(completion) + .done(() -> { logger.debug(String.format("created volume snapshot group[uuid:%s] for vm[uuid:%s]", - group.getUuid(), vm.getUuid())); - completion.success(VolumeSnapshotGroupInventory.valueOf(dbf.reload(group))); - } - - private VolumeSnapshotGroupVO createGroup(CreateVolumesSnapshotReply r) { - List refs = new ArrayList<>(); - VolumeSnapshotGroupVO group = new VolumeSnapshotGroupVO(); - if (msg.getResourceUuid() != null) { - group.setUuid(msg.getResourceUuid()); - } else { - group.setUuid(getUuid()); - } - group.setSnapshotCount(cmsg.getVolumeSnapshotJobs().size()); - group.setName(msg.getName()); - group.setDescription(msg.getDescription()); - group.setVmInstanceUuid(vm.getUuid()); - group.setAccountUuid(msg.getSession().getAccountUuid()); - for (VolumeSnapshotInventory inv : r.getInventories()) { - VolumeSnapshotGroupRefVO ref = new VolumeSnapshotGroupRefVO(); - ref.setVolumeUuid(inv.getVolumeUuid()); - ref.setVolumeName(vols.get(inv.getVolumeUuid()).getName()); - ref.setVolumeType(inv.getVolumeType()); - ref.setVolumeSnapshotGroupUuid(group.getUuid()); - ref.setVolumeSnapshotUuid(inv.getUuid()); - ref.setVolumeSnapshotName(inv.getName()); - ref.setVolumeSnapshotInstallPath(inv.getPrimaryStorageInstallPath()); - ref.setDeviceId(vols.get(inv.getVolumeUuid()).getDeviceId()); - ref.setVolumeLastAttachDate(vols.get(inv.getVolumeUuid()).getLastAttachDate()); - refs.add(ref); - } - - dbf.persist(group); - dbf.persistCollection(refs); - return group; - } - }); + groupRef.get().getUuid(), vm.getUuid())); + completion.success(VolumeSnapshotGroupInventory.valueOf(dbf.reload(groupRef.get()))); + }) + .error(completion::fail) + .start(); } private void handle(APIFlattenVolumeMsg msg) { diff --git a/test/pom.xml b/test/pom.xml index e054377d284..3f81ded3240 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -9,7 +9,7 @@ zstack org.zstack - 4.10.0 + 4.10.0 .. test diff --git a/test/src/test/groovy/org/zstack/test/integration/db/schema/CheckNotNullFieldCase.groovy b/test/src/test/groovy/org/zstack/test/integration/db/schema/CheckNotNullFieldCase.groovy index ce902e842ab..77d411b2e7f 100644 --- a/test/src/test/groovy/org/zstack/test/integration/db/schema/CheckNotNullFieldCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/db/schema/CheckNotNullFieldCase.groovy @@ -3,8 +3,6 @@ package org.zstack.test.integration.db.schema import org.zstack.core.db.Q import org.zstack.header.vo.ResourceVO import org.zstack.header.vo.ResourceVO_ -import org.zstack.header.identity.AccountResourceRefVO -import org.zstack.header.identity.AccountResourceRefVO_ import org.zstack.testlib.EnvSpec import org.zstack.testlib.SubCase import org.zstack.utils.VersionComparator @@ -31,7 +29,11 @@ class CheckNotNullFieldCase extends SubCase{ @Override void test() { - String upgradeSchemaDir = Paths.get("../conf/db/upgrade").toAbsolutePath().normalize().toString() + checkNotNullField(Paths.get("../conf/db/upgrade").toAbsolutePath().normalize().toString()) + checkNotNullField(Paths.get("../conf/db/zsv").toAbsolutePath().normalize().toString()) + } + + static void checkNotNullField(String upgradeSchemaDir) { File dir = new File(upgradeSchemaDir) dir.eachFileRecurse { schema -> if (!schema.name.contains("__")){ diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/HostSecretKvmAgentSimulatorCase.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/HostSecretKvmAgentSimulatorCase.groovy new file mode 100644 index 00000000000..c3f9925d3da --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/HostSecretKvmAgentSimulatorCase.groovy @@ -0,0 +1,133 @@ +package org.zstack.test.integration.kvm.tpm + +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.zstack.header.Constants +import org.zstack.header.secret.SecretHostGetReply +import org.zstack.kvm.KVMAgentCommands +import org.zstack.kvm.KVMConstant +import org.zstack.test.integration.kvm.KvmTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.KVMSimulator +import org.zstack.testlib.SubCase +import org.zstack.utils.gson.JSONObjectUtil + +class HostSecretKvmAgentSimulatorCase extends SubCase { + EnvSpec env + + @Override + void setup() { + useSpring(KvmTest.springSpec) + } + + @Override + void clean() { + env?.delete() + } + + @Override + void environment() { + env = makeEnv { + zone { + name = "zone" + cluster { + name = "cluster" + kvm { + name = "kvm1" + managementIp = "127.0.0.12" + username = "root" + password = "password" + } + } + } + } + } + + @Override + void test() { + env.create { + testSimulatedGetSecretErrorMatchesMnConstant() + testEnsureThenGetReturnsSameUuid() + testDeleteSecretRemovesCacheEntry() + } + } + + void testSimulatedGetSecretErrorMatchesMnConstant() { + KVMSimulator.resetSimulatedHostSecretCache() + def hostUuid = "sim-host-1" + def cmd = new KVMAgentCommands.SecretHostGetCmd() + cmd.vmUuid = "sim-vm-1" + cmd.purpose = "vtpm" + cmd.keyVersion = 1 + cmd.usageInstance = KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM + def headers = new HttpHeaders() + headers.add(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, hostUuid) + def entity = new HttpEntity(JSONObjectUtil.toJsonString(cmd), headers) + + def sim = env.getSimulator(KVMConstant.KVM_GET_SECRET_PATH) + assert sim != null + def rsp = sim(entity) as KVMAgentCommands.SecretHostGetResponse + assert !rsp.success + assert SecretHostGetReply.ERROR_CODE_SECRET_NOT_FOUND == rsp.error + } + + void testEnsureThenGetReturnsSameUuid() { + KVMSimulator.resetSimulatedHostSecretCache() + def hostUuid = "sim-host-2" + def vmUuid = "sim-vm-2" + def purpose = "vtpm" + def keyVersion = 2 + + def ensureCmd = new KVMAgentCommands.SecretHostDefineCmd() + ensureCmd.vmUuid = vmUuid + ensureCmd.purpose = purpose + ensureCmd.keyVersion = keyVersion + ensureCmd.encryptedDek = "ZHVtbXk=" + ensureCmd.description = "" + ensureCmd.usageInstance = KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM + + def headers = new HttpHeaders() + headers.add(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, hostUuid) + def ensureEntity = new HttpEntity(JSONObjectUtil.toJsonString(ensureCmd), headers) + def ensureSim = env.getSimulator(KVMConstant.KVM_ENSURE_SECRET_PATH) + def ensureRsp = ensureSim(ensureEntity) as KVMAgentCommands.SecretHostDefineResponse + assert ensureRsp.success + assert ensureRsp.secretUuid != null + + def getCmd = new KVMAgentCommands.SecretHostGetCmd() + getCmd.vmUuid = vmUuid + getCmd.purpose = purpose + getCmd.keyVersion = keyVersion + getCmd.usageInstance = KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM + def getEntity = new HttpEntity(JSONObjectUtil.toJsonString(getCmd), headers) + def getSim = env.getSimulator(KVMConstant.KVM_GET_SECRET_PATH) + def getRsp = getSim(getEntity) as KVMAgentCommands.SecretHostGetResponse + assert getRsp.success + assert ensureRsp.secretUuid == getRsp.secretUuid + } + + void testDeleteSecretRemovesCacheEntry() { + KVMSimulator.resetSimulatedHostSecretCache() + def hostUuid = "sim-host-3" + def vmUuid = "sim-vm-3" + def purpose = "vtpm" + def keyVersion = 3 + + KVMSimulator.putSimulatedHostSecretForTest(hostUuid, vmUuid, purpose, keyVersion, "to-be-removed") + + def delCmd = new KVMAgentCommands.SecretHostDeleteCmd() + delCmd.vmUuid = vmUuid + delCmd.purpose = purpose + delCmd.keyVersion = keyVersion + delCmd.usageInstance = KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM + def headers = new HttpHeaders() + headers.add(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, hostUuid) + def delEntity = new HttpEntity(JSONObjectUtil.toJsonString(delCmd), headers) + def delSim = env.getSimulator(KVMConstant.KVM_DELETE_SECRET_PATH) + assert delSim != null + def delRsp = delSim(delEntity) as KVMAgentCommands.SecretHostDeleteResponse + assert delRsp.success + + assert KVMSimulator.getSimulatedHostSecretForTest(hostUuid, vmUuid, purpose, keyVersion) == null + } +} diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/KvmTpmHostSecretSimulatorTest.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/KvmTpmHostSecretSimulatorTest.groovy new file mode 100644 index 00000000000..13b60f51a67 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/tpm/KvmTpmHostSecretSimulatorTest.groovy @@ -0,0 +1,10 @@ +package org.zstack.test.integration.kvm.tpm + +import org.zstack.test.integration.kvm.KvmTest + +/** + * Runs {@link HostSecretKvmAgentSimulatorCase} against {@link org.zstack.testlib.KVMSimulator} + * host secret HTTP handlers (getSecret / ensureSecret / deleteSecret). + */ +class KvmTpmHostSecretSimulatorTest extends KvmTest { +} diff --git a/test/src/test/resources/springConfigXml/Kvm.xml b/test/src/test/resources/springConfigXml/Kvm.xml index 137cad827f6..60e6072849f 100755 --- a/test/src/test/resources/springConfigXml/Kvm.xml +++ b/test/src/test/resources/springConfigXml/Kvm.xml @@ -159,9 +159,10 @@ - + + @@ -242,4 +243,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 515c1f6ce99..a869b82386d 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -5282,6 +5282,33 @@ abstract class ApiHelper { } + def checkPrimaryStorageConsistency(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CheckPrimaryStorageConsistencyAction.class) Closure c) { + def a = new org.zstack.sdk.CheckPrimaryStorageConsistencyAction() + 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 checkScsiLunClusterStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CheckScsiLunClusterStatusAction.class) Closure c) { def a = new org.zstack.sdk.CheckScsiLunClusterStatusAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -5606,6 +5633,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 +6983,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 @@ -17648,6 +17675,33 @@ abstract class ApiHelper { } + def discoverSharedBlockGroupVgs(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DiscoverSharedBlockGroupVgsAction.class) Closure c) { + def a = new org.zstack.sdk.DiscoverSharedBlockGroupVgsAction() + 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 getSignatureServerEncryptPublicKey(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetSignatureServerEncryptPublicKeyAction.class) Closure c) { def a = new org.zstack.sdk.GetSignatureServerEncryptPublicKeyAction() @@ -18566,6 +18620,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 +27487,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 +28729,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 @@ -30484,6 +30619,33 @@ abstract class ApiHelper { } + def takeoverPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.TakeoverPrimaryStorageAction.class) Closure c) { + def a = new org.zstack.sdk.TakeoverPrimaryStorageAction() + 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 triggerGCJob(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.TriggerGCJobAction.class) Closure c) { def a = new org.zstack.sdk.TriggerGCJobAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -32239,35 +32401,8 @@ abstract class ApiHelper { } - 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 - 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 updateImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImageAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateImageAction() + 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 @@ -32293,8 +32428,8 @@ abstract class ApiHelper { } - def updateImagePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImagePackageAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateImagePackageAction() + 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 c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32320,8 +32455,8 @@ abstract class ApiHelper { } - def updateImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImageStoreBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateImageStoreBackupStorageAction() + def updateImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImageAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateImageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32347,8 +32482,8 @@ abstract class ApiHelper { } - def updateInfoSecSecretResourcePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInfoSecSecretResourcePoolAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateInfoSecSecretResourcePoolAction() + def updateImagePackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImagePackageAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateImagePackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32374,8 +32509,8 @@ abstract class ApiHelper { } - def updateInfoSecSecurityMachine(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInfoSecSecurityMachineAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateInfoSecSecurityMachineAction() + def updateImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateImageStoreBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateImageStoreBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32401,8 +32536,8 @@ abstract class ApiHelper { } - def updateInstanceOffering(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInstanceOfferingAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateInstanceOfferingAction() + def updateInfoSecSecretResourcePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInfoSecSecretResourcePoolAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateInfoSecSecretResourcePoolAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32428,8 +32563,62 @@ abstract class ApiHelper { } - def updateIpRange(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateIpRangeAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateIpRangeAction() + def updateInfoSecSecurityMachine(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInfoSecSecurityMachineAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateInfoSecSecurityMachineAction() + 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 updateInstanceOffering(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateInstanceOfferingAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateInstanceOfferingAction() + 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 updateIpRange(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateIpRangeAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateIpRangeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -34210,6 +34399,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 @@ -36525,13 +36741,15 @@ abstract class ApiHelper { } - def getManagementNodesStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.common.GetManagementNodesStatusAction.class) Closure c) { - def a = new org.zstack.sdk.managements.common.GetManagementNodesStatusAction() + def queryKeyProvider(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.api.QueryKeyProviderAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.api.QueryKeyProviderAction() 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) { @@ -36552,8 +36770,8 @@ abstract class ApiHelper { } - def getZSha2Status(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.ha2.GetZSha2StatusAction.class) Closure c) { - def a = new org.zstack.sdk.managements.ha2.GetZSha2StatusAction() + def rekeyKeyProviderRefs(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.api.RekeyKeyProviderRefsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.api.RekeyKeyProviderRefsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36579,8 +36797,8 @@ abstract class ApiHelper { } - def zSha2Demote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.ha2.ZSha2DemoteAction.class) Closure c) { - def a = new org.zstack.sdk.managements.ha2.ZSha2DemoteAction() + def createKms(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.CreateKmsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.CreateKmsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36606,8 +36824,8 @@ abstract class ApiHelper { } - def addSNSSmsReceiver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.AddSNSSmsReceiverAction.class) Closure c) { - def a = new org.zstack.sdk.sns.AddSNSSmsReceiverAction() + def deleteKms(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.DeleteKmsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.DeleteKmsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36633,8 +36851,8 @@ abstract class ApiHelper { } - def changeSNSApplicationEndpointState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSApplicationEndpointStateAction.class) Closure c) { - def a = new org.zstack.sdk.sns.ChangeSNSApplicationEndpointStateAction() + def getKmsServerCertFromKms(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.GetKmsServerCertFromKmsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.GetKmsServerCertFromKmsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36660,13 +36878,15 @@ abstract class ApiHelper { } - def changeSNSApplicationPlatformState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSApplicationPlatformStateAction.class) Closure c) { - def a = new org.zstack.sdk.sns.ChangeSNSApplicationPlatformStateAction() + def queryKms(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.QueryKmsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.QueryKmsAction() 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) { @@ -36687,8 +36907,8 @@ abstract class ApiHelper { } - def changeSNSTopicState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSTopicStateAction.class) Closure c) { - def a = new org.zstack.sdk.sns.ChangeSNSTopicStateAction() + def updateKms(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.UpdateKmsAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.UpdateKmsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36714,8 +36934,8 @@ abstract class ApiHelper { } - def createSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.CreateSNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.CreateSNSTopicAction() + def uploadKmsClientCsr(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.UploadKmsClientCsrAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientCsrAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36741,8 +36961,8 @@ abstract class ApiHelper { } - def deleteSNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSApplicationEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.DeleteSNSApplicationEndpointAction() + def uploadKmsClientIdentity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.UploadKmsClientIdentityAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientIdentityAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36768,8 +36988,8 @@ abstract class ApiHelper { } - def deleteSNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSApplicationPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.DeleteSNSApplicationPlatformAction() + def uploadKmsClientSignedCert(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.UploadKmsClientSignedCertAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.UploadKmsClientSignedCertAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36795,8 +37015,8 @@ abstract class ApiHelper { } - def deleteSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.DeleteSNSTopicAction() + def uploadKmsServerCert(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.kms.api.UploadKmsServerCertAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.kms.api.UploadKmsServerCertAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36822,15 +37042,13 @@ abstract class ApiHelper { } - def querySNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSApplicationEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.QuerySNSApplicationEndpointAction() + def backupNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.BackupNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.BackupNkpAction() 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) { @@ -36851,15 +37069,13 @@ abstract class ApiHelper { } - def querySNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSApplicationPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.QuerySNSApplicationPlatformAction() + def createNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.CreateNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.CreateNkpAction() 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) { @@ -36880,15 +37096,13 @@ abstract class ApiHelper { } - def querySNSSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSSmsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.QuerySNSSmsEndpointAction() + def deleteNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.DeleteNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.DeleteNkpAction() 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) { @@ -36909,15 +37123,13 @@ abstract class ApiHelper { } - def querySNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.QuerySNSTopicAction() + def parseNkpRestore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.ParseNkpRestoreAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.ParseNkpRestoreAction() 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) { @@ -36938,8 +37150,8 @@ abstract class ApiHelper { } - def querySNSTopicSubscriber(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSTopicSubscriberAction.class) Closure c) { - def a = new org.zstack.sdk.sns.QuerySNSTopicSubscriberAction() + def queryNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.QueryNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.QueryNkpAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36967,8 +37179,8 @@ abstract class ApiHelper { } - def removeSNSSmsReceiver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.RemoveSNSSmsReceiverAction.class) Closure c) { - def a = new org.zstack.sdk.sns.RemoveSNSSmsReceiverAction() + def restoreNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.RestoreNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.RestoreNkpAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -36994,8 +37206,8 @@ abstract class ApiHelper { } - def subscribeSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.SubscribeSNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.SubscribeSNSTopicAction() + def updateNkp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.keyprovider.nkp.api.UpdateNkpAction.class) Closure c) { + def a = new org.zstack.sdk.keyprovider.nkp.api.UpdateNkpAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37021,8 +37233,8 @@ abstract class ApiHelper { } - def unsubscribeSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UnsubscribeSNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.UnsubscribeSNSTopicAction() + def getManagementNodesStatus(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.common.GetManagementNodesStatusAction.class) Closure c) { + def a = new org.zstack.sdk.managements.common.GetManagementNodesStatusAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37048,8 +37260,8 @@ abstract class ApiHelper { } - def updateSNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSApplicationEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.UpdateSNSApplicationEndpointAction() + def getZSha2Status(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.ha2.GetZSha2StatusAction.class) Closure c) { + def a = new org.zstack.sdk.managements.ha2.GetZSha2StatusAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37075,8 +37287,8 @@ abstract class ApiHelper { } - def updateSNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSApplicationPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.UpdateSNSApplicationPlatformAction() + def zSha2Demote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.managements.ha2.ZSha2DemoteAction.class) Closure c) { + def a = new org.zstack.sdk.managements.ha2.ZSha2DemoteAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37102,8 +37314,8 @@ abstract class ApiHelper { } - def updateSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSTopicAction.class) Closure c) { - def a = new org.zstack.sdk.sns.UpdateSNSTopicAction() + def addSNSSmsReceiver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.AddSNSSmsReceiverAction.class) Closure c) { + def a = new org.zstack.sdk.sns.AddSNSSmsReceiverAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37129,8 +37341,8 @@ abstract class ApiHelper { } - def addSNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.AddSNSDingTalkAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.AddSNSDingTalkAtPersonAction() + def changeSNSApplicationEndpointState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSApplicationEndpointStateAction.class) Closure c) { + def a = new org.zstack.sdk.sns.ChangeSNSApplicationEndpointStateAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37156,8 +37368,8 @@ abstract class ApiHelper { } - def createSNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.CreateSNSDingTalkEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.CreateSNSDingTalkEndpointAction() + def changeSNSApplicationPlatformState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSApplicationPlatformStateAction.class) Closure c) { + def a = new org.zstack.sdk.sns.ChangeSNSApplicationPlatformStateAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37183,15 +37395,13 @@ abstract class ApiHelper { } - def querySNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkAtPersonAction() + def changeSNSTopicState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.ChangeSNSTopicStateAction.class) Closure c) { + def a = new org.zstack.sdk.sns.ChangeSNSTopicStateAction() 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) { @@ -37212,15 +37422,13 @@ abstract class ApiHelper { } - def querySNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkEndpointAction() + def createSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.CreateSNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.CreateSNSTopicAction() 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) { @@ -37241,8 +37449,8 @@ abstract class ApiHelper { } - def removeSNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.RemoveSNSDingTalkAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.RemoveSNSDingTalkAtPersonAction() + def deleteSNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSApplicationEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.DeleteSNSApplicationEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37268,8 +37476,8 @@ abstract class ApiHelper { } - def sNSDingTalkTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.SNSDingTalkTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.SNSDingTalkTestConnectionAction() + def deleteSNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSApplicationPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.DeleteSNSApplicationPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37295,8 +37503,8 @@ abstract class ApiHelper { } - def updateAtPersonOfAtDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.UpdateAtPersonOfAtDingTalkEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.UpdateAtPersonOfAtDingTalkEndpointAction() + def deleteSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.DeleteSNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.DeleteSNSTopicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37322,40 +37530,15 @@ abstract class ApiHelper { } - def updateSNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.UpdateSNSDingTalkEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.dingtalk.UpdateSNSDingTalkEndpointAction() + def querySNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSApplicationEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.QuerySNSApplicationEndpointAction() 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()) - } - } - - - def addEmailAddressToSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.AddEmailAddressToSNSEmailEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.AddEmailAddressToSNSEmailEndpointAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -37376,40 +37559,15 @@ abstract class ApiHelper { } - def createSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.CreateSNSEmailEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.CreateSNSEmailEndpointAction() + def querySNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSApplicationPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.QuerySNSApplicationPlatformAction() 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()) - } - } - - - def createSNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.CreateSNSEmailPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.CreateSNSEmailPlatformAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - if (System.getProperty("apipath") != null) { if (a.apiId == null) { @@ -37430,13 +37588,15 @@ abstract class ApiHelper { } - def deleteEmailAddressOfSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.DeleteEmailAddressOfSNSEmailEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.DeleteEmailAddressOfSNSEmailEndpointAction() + def querySNSSmsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSSmsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.QuerySNSSmsEndpointAction() 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) { @@ -37457,8 +37617,8 @@ abstract class ApiHelper { } - def querySNSEmailAddress(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailAddressAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailAddressAction() + def querySNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.QuerySNSTopicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37486,8 +37646,8 @@ abstract class ApiHelper { } - def querySNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailEndpointAction() + def querySNSTopicSubscriber(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.QuerySNSTopicSubscriberAction.class) Closure c) { + def a = new org.zstack.sdk.sns.QuerySNSTopicSubscriberAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37515,15 +37675,13 @@ abstract class ApiHelper { } - def querySNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailPlatformAction() + def removeSNSSmsReceiver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.RemoveSNSSmsReceiverAction.class) Closure c) { + def a = new org.zstack.sdk.sns.RemoveSNSSmsReceiverAction() 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) { @@ -37544,8 +37702,8 @@ abstract class ApiHelper { } - def sNSEmailTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.SNSEmailTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.SNSEmailTestConnectionAction() + def subscribeSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.SubscribeSNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.SubscribeSNSTopicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37571,8 +37729,8 @@ abstract class ApiHelper { } - def updateEmailAddressOfSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.UpdateEmailAddressOfSNSEmailEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.UpdateEmailAddressOfSNSEmailEndpointAction() + def unsubscribeSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UnsubscribeSNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.UnsubscribeSNSTopicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37598,8 +37756,8 @@ abstract class ApiHelper { } - def validateSNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.ValidateSNSEmailPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.email.ValidateSNSEmailPlatformAction() + def updateSNSApplicationEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSApplicationEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.UpdateSNSApplicationEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37625,8 +37783,8 @@ abstract class ApiHelper { } - def addSNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.AddSNSFeiShuAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.AddSNSFeiShuAtPersonAction() + def updateSNSApplicationPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSApplicationPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.UpdateSNSApplicationPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37652,8 +37810,8 @@ abstract class ApiHelper { } - def createSNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.CreateSNSFeiShuEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.CreateSNSFeiShuEndpointAction() + def updateSNSTopic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.UpdateSNSTopicAction.class) Closure c) { + def a = new org.zstack.sdk.sns.UpdateSNSTopicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37679,8 +37837,62 @@ abstract class ApiHelper { } - def querySNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuAtPersonAction() + def addSNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.AddSNSDingTalkAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.AddSNSDingTalkAtPersonAction() + 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 createSNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.CreateSNSDingTalkEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.CreateSNSDingTalkEndpointAction() + 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 querySNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37708,8 +37920,8 @@ abstract class ApiHelper { } - def querySNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuEndpointAction() + def querySNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.QuerySNSDingTalkEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37737,8 +37949,8 @@ abstract class ApiHelper { } - def removeSNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.RemoveSNSFeiShuAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.RemoveSNSFeiShuAtPersonAction() + def removeSNSDingTalkAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.RemoveSNSDingTalkAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.RemoveSNSDingTalkAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37764,8 +37976,8 @@ abstract class ApiHelper { } - def sNSFeiShuTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.SNSFeiShuTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.SNSFeiShuTestConnectionAction() + def sNSDingTalkTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.SNSDingTalkTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.SNSDingTalkTestConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37791,8 +38003,8 @@ abstract class ApiHelper { } - def updateAtPersonOfAtFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.UpdateAtPersonOfAtFeiShuEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.UpdateAtPersonOfAtFeiShuEndpointAction() + def updateAtPersonOfAtDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.UpdateAtPersonOfAtDingTalkEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.UpdateAtPersonOfAtDingTalkEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37818,8 +38030,8 @@ abstract class ApiHelper { } - def updateSNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.UpdateSNSFeiShuEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.feishu.UpdateSNSFeiShuEndpointAction() + def updateSNSDingTalkEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.dingtalk.UpdateSNSDingTalkEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.dingtalk.UpdateSNSDingTalkEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37845,8 +38057,8 @@ abstract class ApiHelper { } - def createSNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.CreateSNSHttpEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.http.CreateSNSHttpEndpointAction() + def addEmailAddressToSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.AddEmailAddressToSNSEmailEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.AddEmailAddressToSNSEmailEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37872,16 +38084,41 @@ abstract class ApiHelper { } - def querySNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.QuerySNSHttpEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.http.QuerySNSHttpEndpointAction() + def createSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.CreateSNSEmailEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.CreateSNSEmailEndpointAction() 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()) + } + } + def createSNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.CreateSNSEmailPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.CreateSNSEmailPlatformAction() + 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 @@ -37901,8 +38138,8 @@ abstract class ApiHelper { } - def sNSHttpTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.SNSHttpTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.http.SNSHttpTestConnectionAction() + def deleteEmailAddressOfSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.DeleteEmailAddressOfSNSEmailEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.DeleteEmailAddressOfSNSEmailEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37928,13 +38165,15 @@ abstract class ApiHelper { } - def updateSNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.UpdateSNSHttpEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.http.UpdateSNSHttpEndpointAction() + def querySNSEmailAddress(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailAddressAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailAddressAction() 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) { @@ -37955,13 +38194,15 @@ abstract class ApiHelper { } - def createSNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.CreateSNSMicrosoftTeamsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.microsoftteams.CreateSNSMicrosoftTeamsEndpointAction() + def querySNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailEndpointAction() 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) { @@ -37982,8 +38223,8 @@ abstract class ApiHelper { } - def querySNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.QuerySNSMicrosoftTeamsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.microsoftteams.QuerySNSMicrosoftTeamsEndpointAction() + def querySNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.QuerySNSEmailPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.QuerySNSEmailPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38011,8 +38252,8 @@ abstract class ApiHelper { } - def sNSMicrosoftTeamsTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.SNSMicrosoftTeamsTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.microsoftteams.SNSMicrosoftTeamsTestConnectionAction() + def sNSEmailTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.SNSEmailTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.SNSEmailTestConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38038,8 +38279,8 @@ abstract class ApiHelper { } - def updateSNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.UpdateSNSMicrosoftTeamsEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.microsoftteams.UpdateSNSMicrosoftTeamsEndpointAction() + def updateEmailAddressOfSNSEmailEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.UpdateEmailAddressOfSNSEmailEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.UpdateEmailAddressOfSNSEmailEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38065,8 +38306,8 @@ abstract class ApiHelper { } - def createSNSSnmpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpEndpointAction() + def validateSNSEmailPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.email.ValidateSNSEmailPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.email.ValidateSNSEmailPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38092,8 +38333,8 @@ abstract class ApiHelper { } - def createSNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpPlatformAction() + def addSNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.AddSNSFeiShuAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.AddSNSFeiShuAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38119,8 +38360,35 @@ abstract class ApiHelper { } - def querySNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.QuerySNSSnmpPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.snmp.QuerySNSSnmpPlatformAction() + def createSNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.CreateSNSFeiShuEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.CreateSNSFeiShuEndpointAction() + 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 querySNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38148,13 +38416,15 @@ abstract class ApiHelper { } - def sNSSnmpTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.SNSSnmpTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.snmp.SNSSnmpTestConnectionAction() + def querySNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.QuerySNSFeiShuEndpointAction() 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) { @@ -38175,8 +38445,8 @@ abstract class ApiHelper { } - def updateSNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.UpdateSNSSnmpPlatformAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.snmp.UpdateSNSSnmpPlatformAction() + def removeSNSFeiShuAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.RemoveSNSFeiShuAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.RemoveSNSFeiShuAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38202,8 +38472,8 @@ abstract class ApiHelper { } - def addSNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.AddSNSWeComAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.AddSNSWeComAtPersonAction() + def sNSFeiShuTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.SNSFeiShuTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.SNSFeiShuTestConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38229,8 +38499,8 @@ abstract class ApiHelper { } - def createSNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.CreateSNSWeComEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.CreateSNSWeComEndpointAction() + def updateAtPersonOfAtFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.UpdateAtPersonOfAtFeiShuEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.UpdateAtPersonOfAtFeiShuEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38256,16 +38526,41 @@ abstract class ApiHelper { } - def querySNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.QuerySNSWeComAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.QuerySNSWeComAtPersonAction() + def updateSNSFeiShuEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.feishu.UpdateSNSFeiShuEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.feishu.UpdateSNSFeiShuEndpointAction() 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()) + } + } + def createSNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.CreateSNSHttpEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.http.CreateSNSHttpEndpointAction() + 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 @@ -38285,8 +38580,8 @@ abstract class ApiHelper { } - def querySNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.QuerySNSWeComEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.QuerySNSWeComEndpointAction() + def querySNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.QuerySNSHttpEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.http.QuerySNSHttpEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38314,8 +38609,8 @@ abstract class ApiHelper { } - def removeSNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.RemoveSNSWeComAtPersonAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.RemoveSNSWeComAtPersonAction() + def sNSHttpTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.SNSHttpTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.http.SNSHttpTestConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38341,8 +38636,8 @@ abstract class ApiHelper { } - def sNSWeComTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.SNSWeComTestConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.SNSWeComTestConnectionAction() + def updateSNSHttpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.http.UpdateSNSHttpEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.http.UpdateSNSHttpEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38368,8 +38663,8 @@ abstract class ApiHelper { } - def updateAtPersonOfAtWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.UpdateAtPersonOfAtWeComEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.UpdateAtPersonOfAtWeComEndpointAction() + def createSNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.CreateSNSMicrosoftTeamsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.microsoftteams.CreateSNSMicrosoftTeamsEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38395,13 +38690,15 @@ abstract class ApiHelper { } - def updateSNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.UpdateSNSWeComEndpointAction.class) Closure c) { - def a = new org.zstack.sdk.sns.platform.wecom.UpdateSNSWeComEndpointAction() + def querySNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.QuerySNSMicrosoftTeamsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.microsoftteams.QuerySNSMicrosoftTeamsEndpointAction() 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) { @@ -38422,8 +38719,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 sNSMicrosoftTeamsTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.SNSMicrosoftTeamsTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.microsoftteams.SNSMicrosoftTeamsTestConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38449,8 +38746,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 updateSNSMicrosoftTeamsEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.microsoftteams.UpdateSNSMicrosoftTeamsEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.microsoftteams.UpdateSNSMicrosoftTeamsEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38476,8 +38773,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 createSNSSnmpEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpEndpointAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38503,8 +38800,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 createSNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.snmp.CreateSNSSnmpPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38530,8 +38827,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 querySNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.QuerySNSSnmpPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.snmp.QuerySNSSnmpPlatformAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38559,8 +38856,116 @@ 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 sNSSnmpTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.SNSSnmpTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.snmp.SNSSnmpTestConnectionAction() + 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 updateSNSSnmpPlatform(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.snmp.UpdateSNSSnmpPlatformAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.snmp.UpdateSNSSnmpPlatformAction() + 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 addSNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.AddSNSWeComAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.AddSNSWeComAtPersonAction() + 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 createSNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.CreateSNSWeComEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.CreateSNSWeComEndpointAction() + 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 querySNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.QuerySNSWeComAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.QuerySNSWeComAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38588,8 +38993,37 @@ 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 querySNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.QuerySNSWeComEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.QuerySNSWeComEndpointAction() + 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()) + } + } + + + def removeSNSWeComAtPerson(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.RemoveSNSWeComAtPersonAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.RemoveSNSWeComAtPersonAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38615,25 +39049,107 @@ 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 sNSWeComTestConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.SNSWeComTestConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.SNSWeComTestConnectionAction() + 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 updateAtPersonOfAtWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.UpdateAtPersonOfAtWeComEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.UpdateAtPersonOfAtWeComEndpointAction() 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 updateSNSWeComEndpoint(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.sns.platform.wecom.UpdateSNSWeComEndpointAction.class) Closure c) { + def a = new org.zstack.sdk.sns.platform.wecom.UpdateSNSWeComEndpointAction() + 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 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 + 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()) @@ -38647,19 +39163,20 @@ abstract class ApiHelper { 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()) @@ -38673,19 +39190,20 @@ abstract class ApiHelper { 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()) @@ -38699,16 +39217,20 @@ abstract class ApiHelper { 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()) @@ -38722,7 +39244,144 @@ abstract class ApiHelper { 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()) + } + } + + + 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() + + + 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 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 + 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 addTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.api.AddTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.api.AddTpmAction() + 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 getTpmCapability(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.api.GetTpmCapabilityAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.api.GetTpmCapabilityAction() + 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 queryTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.api.QueryTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.api.QueryTpmAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + a.conditions = a.conditions.collect { it.toString() } @@ -38730,14 +39389,68 @@ 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()) + } + } + + def removeTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.api.RemoveTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.api.RemoveTpmAction() + 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 updateTpm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.tpm.api.UpdateTpmAction.class) Closure c) { + def a = new org.zstack.sdk.tpm.api.UpdateTpmAction() + 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()) @@ -38745,26 +39458,107 @@ 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 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()) + } + } + 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()) + } + } + + + 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()) + } + } + + + 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()) @@ -38772,25 +39566,84 @@ 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 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() } + 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 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()) + } + } + + + 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/KVMSimulator.groovy b/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy index 94fc178245d..e229b0fd4c1 100755 --- a/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy +++ b/testlib/src/main/java/org/zstack/testlib/KVMSimulator.groovy @@ -1,684 +1,792 @@ -package org.zstack.testlib - -import org.springframework.http.HttpEntity -import org.zstack.core.db.Q -import org.zstack.core.db.SQL -import org.zstack.core.db.SQLBatch -import org.zstack.header.Constants -import org.zstack.header.storage.primary.PrimaryStorageVO -import org.zstack.header.storage.primary.PrimaryStorageVO_ -import org.zstack.header.storage.snapshot.TakeSnapshotsOnKvmJobStruct -import org.zstack.header.storage.snapshot.TakeSnapshotsOnKvmResultStruct -import org.zstack.header.vm.VmInstanceState -import org.zstack.header.vm.VmInstanceVO -import org.zstack.header.vm.VmInstanceVO_ -import org.zstack.header.vm.devices.DeviceAddress -import org.zstack.header.vm.devices.VirtualDeviceInfo -import org.zstack.header.volume.VolumeInventory -import org.zstack.header.volume.VolumeVO -import org.zstack.header.volume.VolumeVO_ -import org.zstack.kvm.KVMAgentCommands -import org.zstack.kvm.KVMConstant -import org.zstack.kvm.VolumeTO -import org.zstack.testlib.vfs.Qcow2 -import org.zstack.testlib.vfs.VFS -import org.zstack.testlib.vfs.extensions.VFSPrimaryStorageTakeSnapshotBackend -import org.zstack.testlib.vfs.extensions.VFSSnapshot -import org.zstack.utils.BeanUtils -import org.zstack.utils.data.SizeUnit -import org.zstack.utils.gson.JSONObjectUtil - -import javax.persistence.Tuple -import java.util.concurrent.ConcurrentHashMap - -import static org.zstack.kvm.KVMAgentCommands.* -/** - * Created by xing5 on 2017/6/6. - */ -class KVMSimulator implements Simulator { - static ConcurrentHashMap connectCmdConcurrentHashMap = new ConcurrentHashMap<>() - private static Map takeSnapshotBackends = [:] - - static { - BeanUtils.reflections.getSubTypesOf(VFSPrimaryStorageTakeSnapshotBackend.class).each { clz -> - VFSPrimaryStorageTakeSnapshotBackend backend = clz.getConstructor().newInstance() - VFSPrimaryStorageTakeSnapshotBackend old = takeSnapshotBackends[backend.primaryStorageType] - assert old == null : "duplicated VFSPrimaryStorageTakeSnapshotBackend[type: ${backend.primaryStorageType}]," + - " ${backend} and ${old}" - takeSnapshotBackends[backend.primaryStorageType] = backend - } - } - - static List takeSnapshotByPrimaryStorage(HttpEntity e, EnvSpec spec, List snapshotJobs) { - Map> jobMap = new HashMap<>() - Map primaryStorageTypeMap = new HashMap<>() - snapshotJobs.each { job -> - Tuple tuple = SQL.New("select pri.uuid, pri.type from PrimaryStorageVO pri, VolumeVO vol where" + - " pri.uuid = vol.primaryStorageUuid and vol.uuid = :volUuid", Tuple.class).param("volUuid", job.volumeUuid) - .find() - assert tuple.get(1) : "cannot find primary storage of volume[uuid: ${job.volumeUuid}]" - - jobMap.putIfAbsent((String) tuple.get(0), new ArrayList<>()) - jobMap.get((String) tuple.get(0)).add(job) - primaryStorageTypeMap.putIfAbsent((String) tuple.get(0), (String) tuple.get(1)) - } - - List results = new ArrayList<>() - jobMap.entrySet().each { entry -> - String psUuid = entry.key - List jobs = entry.value - VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageTypeMap.get(psUuid)) - results.addAll(bkd.takeSnapshotsOnVolumes(psUuid, e, spec, jobs)) - } - - - return results - } - - static VFSPrimaryStorageTakeSnapshotBackend getVFSPrimaryStorageTakeSnapshotBackend(String primaryStorageType) { - VFSPrimaryStorageTakeSnapshotBackend bkd = takeSnapshotBackends[primaryStorageType] - assert bkd != null : "cannot find VFSPrimaryStorageTakeSnapshotBackend[type: ${primaryStorageType}]" - return bkd - } - - @Override - void registerSimulators(EnvSpec spec) { - spec.simulator(KVMConstant.KVM_HOST_CAPACITY_PATH) { HttpEntity e, EnvSpec espec -> - def rsp = new KVMAgentCommands.HostCapacityResponse() - - KVMHostSpec kspec = espec.specByUuid(e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID)) - rsp.success = true - - if (kspec == null) { - rsp.usedCpu = 0 - rsp.cpuNum = 8 - rsp.totalMemory = SizeUnit.GIGABYTE.toByte(32) - rsp.usedMemory = 0 - rsp.cpuSpeed = 1 - rsp.cpuSockets = 2 - rsp.cpuCoreNum = 4 - } else { - rsp.usedCpu = kspec.usedCpu - rsp.cpuNum = kspec.totalCpu - rsp.totalMemory = kspec.totalMem - rsp.usedMemory = kspec.usedMem - rsp.cpuSpeed = 1 - rsp.cpuSockets = kspec.cpuSockets - rsp.cpuCoreNum = kspec.cpuCores - } - - return rsp - } - - spec.simulator(KVMConstant.KVM_HARDEN_CONSOLE_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_DELETE_CONSOLE_FIREWALL_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.GET_VM_DEVICE_ADDRESS_PATH) { HttpEntity e -> - def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.GetVmDeviceAddressCmd.class) - def rsp = new KVMAgentCommands.GetVmDeviceAddressRsp() - if (cmd.deviceTOs.keySet().contains(VolumeVO.class.simpleName)) { - rsp.addresses = ["VolumeVO": []] - for (Object o : cmd.deviceTOs.get(VolumeVO.class.simpleName)) { - VolumeTO to = JSONObjectUtil.rehashObject(o, VolumeTO.class) - rsp.addresses[VolumeVO.class.simpleName].add(new KVMAgentCommands.VmDeviceAddressTO( - addressType: "pci", - address: String.format("0000:%02d:00:0", to.deviceId), - deviceType: "disk", - uuid: to.volumeUuid - )) - } - } - - return rsp - } - - spec.simulator(KVMConstant.GET_VIRTUALIZER_INFO_PATH) { HttpEntity e -> - def rsp = new GetVirtualizerInfoRsp() - rsp.hostInfo = new VirtualizerInfoTO() - rsp.hostInfo.version = "4.2.0-627.g36ee592.el7" - rsp.hostInfo.virtualizer = "qemu-kvm" - String hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) - rsp.hostInfo.uuid = hostUuid - - def cmd = JSONObjectUtil.toObject(e.body, GetVirtualizerInfoCmd.class) - rsp.vmInfoList = cmd.vmUuids.collect { vmUuid -> - def to = new VirtualizerInfoTO() - to.uuid = vmUuid - to.version = "4.2.0-627.g36ee592.el7" - to.virtualizer = "qemu-kvm" - return to - } - - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_FACT_PATH) { HttpEntity e -> - def hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) - def rsp = new HostFactResponse() - - rsp.osDistribution = "zstack" - rsp.osRelease = "kvmSimulator" - rsp.osVersion = "0.1" - rsp.qemuImgVersion = "2.0.0" - rsp.libvirtVersion = "1.2.9" - rsp.cpuModelName = "Broadwell" - rsp.cpuProcessorNum = 10 - rsp.cpuThreadsPerCore = 2 - rsp.cpuCoresPerSocket = 5 - rsp.cpuSockets = 1 - rsp.cpuGHz = "2.10" - rsp.hostCpuModelName = "Broadwell @ 2.10GHz" - rsp.ipmiAddress = "None" - rsp.eptFlag = "ept" - rsp.libvirtCapabilities = ["incrementaldrivemirror", "blockcopynetworktarget"] - rsp.powerSupplyModelName = "" - rsp.powerSupplyManufacturer = "" - rsp.hvmCpuFlag = "" - rsp.cpuCache = "64.0,4096.0,16384.0" - rsp.nqn = "nqn.2014-08.org.nvmexpress:uuid:748d0363-8366-44db-803b-146effb96988" - rsp.hostname = "hostname" - rsp.iscsiInitiatorName = "iqn.1994-05.com.redhat:" + hostUuid.substring(0, 12) - rsp.virtualizerInfo = new VirtualizerInfoTO() - rsp.virtualizerInfo.version = "4.2.0-627.g36ee592.el7" - rsp.virtualizerInfo.virtualizer = "qemu-kvm" - return rsp - } - - spec.simulator(KVMConstant.KVM_VM_UPDATE_CPU_QUOTA_PATH) { - return new KVMAgentCommands.UpdateVmCpuQuotaRsp() - } - - spec.simulator(KVMConstant.KVM_VM_UPDATE_PRIORITY_PATH) { - return new KVMAgentCommands.UpdateVmPriorityRsp() - } - - spec.simulator(KVMConstant.KVM_VM_CHECK_STATE) { HttpEntity e -> - KVMAgentCommands.CheckVmStateCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.CheckVmStateCmd.class) - List vms = Q.New(VmInstanceVO.class).in(VmInstanceVO_.uuid, cmd.vmUuids).list() - KVMAgentCommands.CheckVmStateRsp rsp = new KVMAgentCommands.CheckVmStateRsp() - rsp.states = [:] - vms.each { - def kstate = KVMConstant.KvmVmState.fromVmInstanceState(it.state) - if (kstate != null) { - rsp.states[(it.uuid)] = kstate.toString() - } else { - rsp.states[(it.uuid)] = KVMConstant.KvmVmState.Shutdown.toString() - } - } - - return rsp - } - - spec.simulator(KVMConstant.KVM_ATTACH_NIC_PATH) { HttpEntity e -> - KVMAgentCommands.AttachNicCommand cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.AttachNicCommand.class) - KVMAgentCommands.AttachNicResponse rsp = new KVMAgentCommands.AttachNicResponse() - VirtualDeviceInfo deviceInfo = new VirtualDeviceInfo() - deviceInfo.setResourceUuid(cmd.nic.getUuid()) - rsp.setVirtualDeviceInfoList(Arrays.asList(deviceInfo)) - return rsp - } - - spec.simulator(KVMConstant.KVM_DETACH_NIC_PATH) { - return new KVMAgentCommands.DetachNicRsp() - } - - spec.simulator(KVMConstant.KVM_UPDATE_NIC_PATH) { - return new KVMAgentCommands.UpdateNicRsp() - } - - spec.simulator(KVMConstant.KVM_ATTACH_ISO_PATH) { - return new KVMAgentCommands.AttachIsoRsp() - } - - spec.simulator(KVMConstant.KVM_DETACH_ISO_PATH) { - return new KVMAgentCommands.DetachIsoRsp() - } - - spec.simulator(KVMConstant.KVM_MERGE_SNAPSHOT_PATH) { HttpEntity e, EnvSpec espec -> - return new KVMAgentCommands.MergeSnapshotRsp() - } - - VFS.vfsHook(KVMConstant.KVM_MERGE_SNAPSHOT_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> - KVMAgentCommands.MergeSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.MergeSnapshotCmd.class) - VolumeInventory volume = null - String primaryStorageType = null - - new SQLBatch() { - @Override - protected void scripts() { - VolumeVO vol = sql("select vol from VolumeVO vol where vol.vmInstanceUuid = :vmUuid " + - " and vol.deviceId = :deviceId", VolumeVO.class) - .param("vmUuid", cmd.vmUuid) - .param("deviceId", cmd.volume.getDeviceId()) - .find() - - assert vol: "cannot find dest volume[path: ${cmd.destPath}, deviceId: ${cmd.volume.getDeviceId()}] of VM[uuid: ${cmd.vmUuid}] in database" - volume = vol.toInventory() - - primaryStorageType = q(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, vol.primaryStorageUuid).findValue() - assert primaryStorageType: "cannot find primary storage[uuid: ${vol.primaryStorageUuid}]" - } - }.execute() - - VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) - bkd.mergeSnapshots(e, espec, cmd, volume) - } - - spec.simulator(KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH) { HttpEntity e, EnvSpec espec -> - return new KVMAgentCommands.TakeSnapshotResponse() - } - - VFS.vfsHook(KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> - KVMAgentCommands.TakeSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.TakeSnapshotCmd.class) - - VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volumeUuid).find() - assert volume: "cannot find volume[uuid: ${cmd.volumeUuid}]" - String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) - .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() - assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] from volume[uuid: ${volume.uuid}, name: ${volume.name}]" - VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) - - VFSSnapshot snapshot = bkd.takeSnapshot(e, espec, cmd, volume.toInventory() as VolumeInventory) - rsp.newVolumeInstallPath = snapshot.installPath - rsp.snapshotInstallPath = cmd.volumeInstallPath - // size required greater than 0, if no mock value set keep size return 1 - rsp.size = snapshot.size == null || snapshot.size == 0 ? 1 : snapshot.size - return rsp - } - - spec.simulator(KVMConstant.KVM_PING_PATH) { HttpEntity e, EnvSpec espec -> - KVMAgentCommands.PingCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.PingCmd.class) - assert null != cmd - assert null != cmd.hostUuid - - def rsp = new KVMAgentCommands.PingResponse() - rsp.hostUuid = cmd.hostUuid - rsp.version = connectCmdConcurrentHashMap.get(rsp.hostUuid).version - rsp.sendCommandUrl = connectCmdConcurrentHashMap.get(rsp.hostUuid).sendCommandUrl - return rsp - } - - spec.simulator(KVMConstant.KVM_UPDATE_HOST_CONFIGURATION_PATH) { HttpEntity e, EnvSpec espec -> - def rsp = new KVMAgentCommands.UpdateHostConfigurationResponse() - return rsp - } - - spec.simulator(KVMConstant.KVM_CONNECT_PATH) { HttpEntity e -> - Spec.checkHttpCallType(e, true) - KVMAgentCommands.ConnectCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.ConnectCmd.class) - - connectCmdConcurrentHashMap.put(cmd.hostUuid, cmd) - - def rsp = new KVMAgentCommands.ConnectResponse() - rsp.success = true - rsp.libvirtVersion = "1.0.0" - rsp.qemuVersion = "1.3.0" - return rsp - } - - spec.simulator(KVMConstant.KVM_ECHO_PATH) { HttpEntity e -> - Spec.checkHttpCallType(e, true) - return [:] - } - - spec.simulator(KVMConstant.KVM_DETACH_VOLUME) { - return new KVMAgentCommands.DetachDataVolumeResponse() - } - - spec.simulator(KVMConstant.KVM_VM_SYNC_PATH) { HttpEntity e -> - def hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) - - List states = Q.New(VmInstanceVO.class) - .select(VmInstanceVO_.uuid, VmInstanceVO_.state) - .in(VmInstanceVO_.state, [VmInstanceState.Running, VmInstanceState.Unknown]) - .eq(VmInstanceVO_.hostUuid, hostUuid).listTuple() - - def rsp = new KVMAgentCommands.VmSyncResponse() - rsp.states = [:] - states.each { - String vmUuid = it.get(0, String.class) - VmInstanceState state = it.get(1, VmInstanceState.class) - if (state == VmInstanceState.Unknown) { - // host reconnecting will set VMs to Unknown in DB - // the spec.simulator treat them as Running by default - rsp.states[(vmUuid)] = KVMConstant.KvmVmState.Running.toString() - } else { - rsp.states[(vmUuid)] = KVMConstant.KvmVmState.fromVmInstanceState(state).toString() - } - } - rsp.setVmInShutdowns(new ArrayList()) - return rsp - } - - spec.simulator(KVMConstant.KVM_VOLUME_SYNC_PATH) { - return new VolumeSyncRsp() - } - - spec.simulator(KVMConstant.KVM_ATTACH_VOLUME) { HttpEntity e -> - def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.AttachDataVolumeCmd.class) - // assume all data volumes has same deviceType. - if (Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).listValues().stream().distinct().count() == 1) { - assert (cmd.addons["attachedDataVolumes"] as List).stream() - .allMatch({ vol -> vol.deviceType == cmd.volume.deviceType }) - } - return new KVMAgentCommands.AttachDataVolumeResponse() - } - - spec.simulator(KVMConstant.KVM_CHECK_PHYSICAL_NETWORK_INTERFACE_PATH) { HttpEntity e -> - Spec.checkHttpCallType(e, true) - return new KVMAgentCommands.CheckPhysicalNetworkInterfaceResponse() - } - - spec.simulator(KVMConstant.KVM_ADD_INTERFACE_TO_BRIDGE_PATH) { - return new KVMAgentCommands.AddInterfaceToBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_REALIZE_L2NOVLAN_NETWORK_PATH) { - return new KVMAgentCommands.CreateBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_MIGRATE_VM_PATH) { - return new KVMAgentCommands.MigrateVmResponse() - } - - spec.simulator(KVMConstant.KVM_GET_CPU_XML_PATH) { - return new KVMAgentCommands.VmGetCpuXmlResponse() - } - - spec.simulator(KVMConstant.KVM_COMPARE_CPU_FUNCTION_PATH) { - return new KVMAgentCommands.VmCompareCpuFunctionResponse() - } - - spec.simulator(KVMConstant.KVM_UPDATE_L2VLAN_NETWORK_PATH) { - return new KVMAgentCommands.UpdateL2NetworkResponse() - } - - spec.simulator(KVMConstant.KVM_UPDATE_L2VXLAN_NETWORK_PATH) { - return new KVMAgentCommands.UpdateL2NetworkResponse() - } - - spec.simulator(KVMConstant.KVM_CHECK_L2NOVLAN_NETWORK_PATH) { - return new KVMAgentCommands.CheckBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_CHECK_L2VLAN_NETWORK_PATH) { - return new KVMAgentCommands.CheckVlanBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_REALIZE_L2VLAN_NETWORK_PATH) { - return new KVMAgentCommands.CreateVlanBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_CHECK_OVSDPDK_NETWORK_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_REALIZE_OVSDPDK_NETWORK_PATH) { - return new KVMAgentCommands.CreateBridgeResponse() - } - - spec.simulator(KVMConstant.KVM_GENERATE_VDPA_PATH) { HttpEntity e -> - KVMAgentCommands.GenerateVdpaCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.GenerateVdpaCmd.class) - def rsp = new KVMAgentCommands.GenerateVdpaResponse() - - ArrayList nics = cmd.getNics() - def vdpaPaths = new ArrayList() - - nics.each { KVMAgentCommands.NicTO it -> - vdpaPaths.add("/var/run/zstack-vdpa/" + it.bridgeName + "/" + it.nicInternalName) - } - rsp.setVdpaPaths(vdpaPaths) - - return rsp - } - - spec.simulator(KVMConstant.KVM_DELETE_VDPA_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_GENERATE_VHOST_USER_CLIENT_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_DELETE_VHOST_USER_CLIENT_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_SYNC_VM_DEVICEINFO_PATH) { HttpEntity e -> - SyncVmDeviceInfoCmd cmd = JSONObjectUtil.toObject(e.body, SyncVmDeviceInfoCmd.class) - def rsp = new SyncVmDeviceInfoResponse() - - rsp.virtualizerInfo = new VirtualizerInfoTO() - rsp.virtualizerInfo.uuid = cmd.vmInstanceUuid - rsp.virtualizerInfo.virtualizer = "qemu-kvm" - rsp.virtualizerInfo.version = "4.2.0-632.g6a6222b.el7" - - return rsp - } - - spec.simulator(KVMConstant.KVM_START_VM_PATH) { HttpEntity e -> - StartVmCmd cmd = JSONObjectUtil.toObject(e.body, StartVmCmd.class) - assert new HashSet<>(cmd.dataVolumes.deviceId).size() == cmd.dataVolumes.size() - StartVmResponse rsp = new StartVmResponse() - rsp.virtualDeviceInfoList = [] - List pciInfo = new ArrayList() - pciInfo.add(cmd.rootVolume) - pciInfo.addAll(cmd.dataVolumes) - - Integer counter = 0 - pciInfo.each { to -> - VirtualDeviceInfo info = new VirtualDeviceInfo() - info.resourceUuid = to.volumeUuid - info.deviceAddress = new DeviceAddress() - info.deviceAddress.domain = "0000" - info.deviceAddress.bus = "00" - info.deviceAddress.slot = Integer.toHexString(counter) - info.deviceAddress.function = "0" - - counter++ - - rsp.virtualDeviceInfoList.add(info) - } - - rsp.virtualizerInfo = new VirtualizerInfoTO() - rsp.virtualizerInfo.uuid = cmd.vmInstanceUuid - rsp.virtualizerInfo.virtualizer = "qemu-kvm" - rsp.virtualizerInfo.version = "4.2.0-632.g6a6222b.el7" - - return rsp - } - - spec.simulator(KVMConstant.KVM_STOP_VM_PATH) { - return new KVMAgentCommands.StopVmResponse() - } - - spec.simulator(KVMConstant.KVM_PAUSE_VM_PATH) { - return new KVMAgentCommands.PauseVmResponse() - } - - spec.simulator(KVMConstant.KVM_RESUME_VM_PATH) { - return new KVMAgentCommands.ResumeVmResponse() - } - - spec.simulator(KVMConstant.KVM_REBOOT_VM_PATH) { - return new KVMAgentCommands.RebootVmResponse() - } - - spec.simulator(KVMConstant.KVM_DESTROY_VM_PATH) { - return new KVMAgentCommands.DestroyVmResponse() - } - - spec.simulator(KVMConstant.KVM_GET_VNC_PORT_PATH) { - def rsp = new KVMAgentCommands.GetVncPortResponse() - rsp.port = 5900 - return rsp - } - - spec.simulator(KVMConstant.KVM_LOGOUT_ISCSI_PATH) { - return new KVMAgentCommands.LogoutIscsiTargetRsp() - } - - spec.simulator(KVMConstant.KVM_LOGIN_ISCSI_PATH) { - return new KVMAgentCommands.LoginIscsiTargetRsp() - } - - spec.simulator(KVMConstant.KVM_VM_ONLINE_INCREASE_CPU) { - return new KVMAgentCommands.IncreaseCpuResponse() - } - - spec.simulator(KVMConstant.KVM_VM_ONLINE_INCREASE_MEMORY) { - return new KVMAgentCommands.IncreaseMemoryResponse() - } - - spec.simulator(KVMConstant.KVM_UPDATE_HOST_OS_PATH) { - return new KVMAgentCommands.UpdateHostOSRsp() - } - - spec.simulator(KVMConstant.KVM_HOST_UPDATE_DEPENDENCY_PATH) { - return new KVMAgentCommands.UpdateDependencyRsp() - } - - spec.simulator(KVMConstant.HOST_UPDATE_SPICE_CHANNEL_CONFIG_PATH) { - return new KVMAgentCommands.UpdateSpiceChannelConfigResponse() - } - - spec.simulator(KVMConstant.KVM_SCAN_VM_PORT_STATUS) { - return new KVMAgentCommands.ScanVmPortResponse() - } - - spec.simulator(KVMConstant.GET_DEV_CAPACITY) { - KVMAgentCommands.GetDevCapacityResponse rsp = new KVMAgentCommands.GetDevCapacityResponse() - rsp.totalSize = SizeUnit.GIGABYTE.toByte(100) - rsp.availableSize = SizeUnit.GIGABYTE.toByte(80) - return rsp - } - - spec.simulator(KVMConstant.KVM_CONFIG_PRIMARY_VM_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_CONFIG_SECONDARY_VM_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_START_COLO_SYNC_PATH) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_REGISTER_PRIMARY_VM_HEARTBEAT) { - return new KVMAgentCommands.AgentResponse() - } - - spec.simulator(KVMConstant.KVM_HOST_CHECK_FILE_PATH) { HttpEntity e -> - KVMAgentCommands.CheckFileOnHostCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.CheckFileOnHostCmd.class) - KVMAgentCommands.CheckFileOnHostResponse response = new KVMAgentCommands.CheckFileOnHostResponse() - response.existPaths = new HashMap<>() - cmd.paths.forEach({ path -> response.existPaths.put(path, "") }) - return response - } - - spec.simulator(KVMConstant.KVM_HOST_NUMA_PATH) { - def rsp = new KVMAgentCommands.GetHostNUMATopologyResponse() - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_ATTACH_VOLUME_PATH) { - def rsp = new KVMAgentCommands.AttachVolumeRsp() - rsp.device = "/dev/nbd0" - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_DETACH_VOLUME_PATH) { - def rsp = new KVMAgentCommands.DetachVolumeRsp() - return rsp - } - - spec.simulator(KVMConstant.KVM_BLOCK_COMMIT_VOLUME_PATH) { HttpEntity e -> - def rsp = new BlockCommitResponse() - rsp.size = 1 - return rsp - } - - VFS.vfsHook(KVMConstant.KVM_BLOCK_COMMIT_VOLUME_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> - BlockCommitCmd cmd = JSONObjectUtil.toObject(e.body, BlockCommitCmd.class) - - VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volume.getVolumeUuid()).find() - assert volume: "cannot find volume[uuid: ${cmd.volume.getVolumeUuid()}]" - - String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) - .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() - assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] " + - "from volume[uuid: ${volume.uuid}, name: ${volume.name}]" - - VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) - bkd.blockCommit(e, espec, cmd, volume.toInventory() as VolumeInventory) - return rsp - } - - spec.simulator(KVMConstant.KVM_BLOCK_PULL_VOLUME_PATH) { HttpEntity e -> - def rsp = new BlockPullResponse() - rsp.size = 1 - return rsp - } - - VFS.vfsHook(KVMConstant.KVM_BLOCK_PULL_VOLUME_PATH, spec) { BlockPullResponse rsp, HttpEntity e, EnvSpec espec -> - BlockPullCmd cmd = JSONObjectUtil.toObject(e.body, BlockPullCmd.class) - - VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volume.getVolumeUuid()).find() - assert volume: "cannot find volume[uuid: ${cmd.getVolume().getVolumeUuid()}]" - - String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) - .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() - assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] " + - "from volume[uuid: ${volume.uuid}, name: ${volume.name}]" - - VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) - - Qcow2 newInstallPath = bkd.blockPull(e, espec, cmd, volume.toInventory() as VolumeInventory) - rsp.size = newInstallPath.actualSize == 0 ? 1 : newInstallPath.actualSize - return rsp - } - - spec.simulator(KVMConstant.TAKE_VM_CONSOLE_SCREENSHOT_PATH) { - def rsp = new KVMAgentCommands.TakeVmConsoleScreenshotRsp() - rsp.imageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABECAIAAADFvmZTAAACA0lEQVR4nO3VMYviQBQH8Hkvk5hE2ZUEU+juFlbKauH3/wYWNhYWKiiIRBDcZDckJJmZK6Zb7q44uJW7/f+qCcOQzP89XoSAL0FPT09VVTFzEARhGKZp2rat67qO4xhjlFJ5nidJcr1eHx8fPc+r65qZ27Zt2zbP83t//z9DRlFUVRURGWN83/c8z/O8Xq/HzFEUvb29aa211sxsgxZCKKWIKMuyPM9tSWypHMdRSmmtP72DmTudTlmWRCSl1Forpe5x2XuSSZJcLpeiKPr9flEUdV0LIZqm+fj4GI/H+/1+Npsdj8fX11chRFVV5/N5MBgIIZg5juP5fN7tdpfL5WAwiKJovV4Ph8MgCDabzWQyKcvycDgsFoskSVarlZTy+fl5t9udz2db2jvf/gsRETGz1tp13bZtbT8yszHGdqiNw66Z2W4ZY7TWUkqbl5TSGGOPSymFEHb+2Oa1u47jEFHTNFrrbxWxRQ8PD1LKuq7Lsux2u8zcNE1d10EQFEXR6XRc1yWiPM9937cxua6rlPrplIBfkS8vL2EYZlmWpul0OvV9/3Q6vb+/j0aj4/EYx3EYhkqp7XY7HA6TJDHG3G43IjocDnbsfsP2/OuIyP4P4U8Q0W8eAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgf/QDviYImvcayGAAAAAASUVORK5CYII=" - return rsp - } - - spec.simulator(KVMConstant.KVM_UPDATE_HOST_NQN_PATH) { - def rsp = new KVMAgentCommands.UpdateHostNqnRsp() - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PATH) { - DownloadFileResponse rsp = new DownloadFileResponse() - rsp.md5sum = "00df1327d49e4631a21f4467aa729c11" - rsp.size = 1024 - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_FILE_UPLOAD_PATH) { - UploadFileResponse rsp = new UploadFileResponse() - rsp.directUploadPath = "http://172.1.1.1:7070/host/file/direct-upload" - return rsp - } - - spec.simulator(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PROGRESS_PATH) { - GetDownloadFileProgressResponse rsp = new GetDownloadFileProgressResponse() - rsp.completed = false - rsp.downloadSize = 1 - rsp.size = 1024 - rsp.lastOpTime = System.currentTimeMillis() - return rsp - } - - spec.simulator(KVMConstant.KVM_UPDATE_HOSTNAME_PATH) { - return new UpdateHostnameRsp() - } - } -} +package org.zstack.testlib + +import org.springframework.http.HttpEntity +import org.zstack.core.Platform +import org.zstack.core.db.Q +import org.zstack.core.db.SQL +import org.zstack.core.db.SQLBatch +import org.zstack.header.Constants +import org.zstack.header.storage.primary.PrimaryStorageVO +import org.zstack.header.storage.primary.PrimaryStorageVO_ +import org.zstack.header.storage.snapshot.TakeSnapshotsOnKvmJobStruct +import org.zstack.header.storage.snapshot.TakeSnapshotsOnKvmResultStruct +import org.zstack.header.vm.VmInstanceState +import org.zstack.header.vm.VmInstanceVO +import org.zstack.header.vm.VmInstanceVO_ +import org.zstack.header.vm.additions.VmHostFileContentFormat +import org.zstack.header.vm.devices.DeviceAddress +import org.zstack.header.vm.devices.VirtualDeviceInfo +import org.zstack.header.volume.VolumeInventory +import org.zstack.header.volume.VolumeVO +import org.zstack.header.volume.VolumeVO_ +import org.zstack.kvm.KVMAgentCommands +import org.zstack.kvm.KVMConstant +import org.zstack.kvm.VolumeTO +import org.zstack.testlib.vfs.Qcow2 +import org.zstack.testlib.vfs.VFS +import org.zstack.testlib.vfs.extensions.VFSPrimaryStorageTakeSnapshotBackend +import org.zstack.testlib.vfs.extensions.VFSSnapshot +import org.zstack.utils.BeanUtils +import org.zstack.utils.data.SizeUnit +import org.zstack.utils.gson.JSONObjectUtil + +import javax.persistence.Tuple +import java.util.concurrent.ConcurrentHashMap + +import static org.zstack.kvm.KVMAgentCommands.* +/** + * Created by xing5 on 2017/6/6. + */ +class KVMSimulator implements Simulator { + static ConcurrentHashMap connectCmdConcurrentHashMap = new ConcurrentHashMap<>() + static ConcurrentHashMap ensureSecretUuidCache = new ConcurrentHashMap<>() + static final String MOCK_ENVELOPE_PUBLIC_KEY_BASE64 = "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=" + private static Map takeSnapshotBackends = [:] + + static { + BeanUtils.reflections.getSubTypesOf(VFSPrimaryStorageTakeSnapshotBackend.class).each { clz -> + VFSPrimaryStorageTakeSnapshotBackend backend = clz.getConstructor().newInstance() + VFSPrimaryStorageTakeSnapshotBackend old = takeSnapshotBackends[backend.primaryStorageType] + assert old == null : "duplicated VFSPrimaryStorageTakeSnapshotBackend[type: ${backend.primaryStorageType}]," + + " ${backend} and ${old}" + takeSnapshotBackends[backend.primaryStorageType] = backend + } + } + + static List takeSnapshotByPrimaryStorage(HttpEntity e, EnvSpec spec, List snapshotJobs) { + Map> jobMap = new HashMap<>() + Map primaryStorageTypeMap = new HashMap<>() + snapshotJobs.each { job -> + Tuple tuple = SQL.New("select pri.uuid, pri.type from PrimaryStorageVO pri, VolumeVO vol where" + + " pri.uuid = vol.primaryStorageUuid and vol.uuid = :volUuid", Tuple.class).param("volUuid", job.volumeUuid) + .find() + assert tuple.get(1) : "cannot find primary storage of volume[uuid: ${job.volumeUuid}]" + + jobMap.putIfAbsent((String) tuple.get(0), new ArrayList<>()) + jobMap.get((String) tuple.get(0)).add(job) + primaryStorageTypeMap.putIfAbsent((String) tuple.get(0), (String) tuple.get(1)) + } + + List results = new ArrayList<>() + jobMap.entrySet().each { entry -> + String psUuid = entry.key + List jobs = entry.value + VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageTypeMap.get(psUuid)) + results.addAll(bkd.takeSnapshotsOnVolumes(psUuid, e, spec, jobs)) + } + + + return results + } + + static VFSPrimaryStorageTakeSnapshotBackend getVFSPrimaryStorageTakeSnapshotBackend(String primaryStorageType) { + VFSPrimaryStorageTakeSnapshotBackend bkd = takeSnapshotBackends[primaryStorageType] + assert bkd != null : "cannot find VFSPrimaryStorageTakeSnapshotBackend[type: ${primaryStorageType}]" + return bkd + } + + @Override + void registerSimulators(EnvSpec spec) { + spec.simulator(KVMConstant.KVM_HOST_CAPACITY_PATH) { HttpEntity e, EnvSpec espec -> + def rsp = new KVMAgentCommands.HostCapacityResponse() + + KVMHostSpec kspec = espec.specByUuid(e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID)) + rsp.success = true + + if (kspec == null) { + rsp.usedCpu = 0 + rsp.cpuNum = 8 + rsp.totalMemory = SizeUnit.GIGABYTE.toByte(32) + rsp.usedMemory = 0 + rsp.cpuSpeed = 1 + rsp.cpuSockets = 2 + rsp.cpuCoreNum = 4 + } else { + rsp.usedCpu = kspec.usedCpu + rsp.cpuNum = kspec.totalCpu + rsp.totalMemory = kspec.totalMem + rsp.usedMemory = kspec.usedMem + rsp.cpuSpeed = 1 + rsp.cpuSockets = kspec.cpuSockets + rsp.cpuCoreNum = kspec.cpuCores + } + + return rsp + } + + spec.simulator(KVMConstant.KVM_HARDEN_CONSOLE_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_DELETE_CONSOLE_FIREWALL_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.GET_VM_DEVICE_ADDRESS_PATH) { HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.GetVmDeviceAddressCmd.class) + def rsp = new KVMAgentCommands.GetVmDeviceAddressRsp() + if (cmd.deviceTOs.keySet().contains(VolumeVO.class.simpleName)) { + rsp.addresses = ["VolumeVO": []] + for (Object o : cmd.deviceTOs.get(VolumeVO.class.simpleName)) { + VolumeTO to = JSONObjectUtil.rehashObject(o, VolumeTO.class) + rsp.addresses[VolumeVO.class.simpleName].add(new KVMAgentCommands.VmDeviceAddressTO( + addressType: "pci", + address: String.format("0000:%02d:00:0", to.deviceId), + deviceType: "disk", + uuid: to.volumeUuid + )) + } + } + + return rsp + } + + spec.simulator(KVMConstant.GET_VIRTUALIZER_INFO_PATH) { HttpEntity e -> + def rsp = new GetVirtualizerInfoRsp() + rsp.hostInfo = new VirtualizerInfoTO() + rsp.hostInfo.version = "4.2.0-627.g36ee592.el7" + rsp.hostInfo.virtualizer = "qemu-kvm" + String hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + rsp.hostInfo.uuid = hostUuid + + def cmd = JSONObjectUtil.toObject(e.body, GetVirtualizerInfoCmd.class) + rsp.vmInfoList = cmd.vmUuids.collect { vmUuid -> + def to = new VirtualizerInfoTO() + to.uuid = vmUuid + to.version = "4.2.0-627.g36ee592.el7" + to.virtualizer = "qemu-kvm" + return to + } + + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_FACT_PATH) { HttpEntity e -> + def hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + def rsp = new HostFactResponse() + + rsp.osDistribution = "zstack" + rsp.osRelease = "kvmSimulator" + rsp.osVersion = "0.1" + rsp.qemuImgVersion = "2.0.0" + rsp.libvirtVersion = "1.2.9" + rsp.cpuModelName = "Broadwell" + rsp.cpuProcessorNum = 10 + rsp.cpuThreadsPerCore = 2 + rsp.cpuCoresPerSocket = 5 + rsp.cpuSockets = 1 + rsp.cpuGHz = "2.10" + rsp.hostCpuModelName = "Broadwell @ 2.10GHz" + rsp.ipmiAddress = "None" + rsp.eptFlag = "ept" + rsp.libvirtCapabilities = ["incrementaldrivemirror", "blockcopynetworktarget"] + rsp.powerSupplyModelName = "" + rsp.powerSupplyManufacturer = "" + rsp.hvmCpuFlag = "" + rsp.cpuCache = "64.0,4096.0,16384.0" + rsp.nqn = "nqn.2014-08.org.nvmexpress:uuid:748d0363-8366-44db-803b-146effb96988" + rsp.hostname = "hostname" + rsp.iscsiInitiatorName = "iqn.1994-05.com.redhat:" + hostUuid.substring(0, 12) + rsp.virtualizerInfo = new VirtualizerInfoTO() + rsp.virtualizerInfo.version = "4.2.0-627.g36ee592.el7" + rsp.virtualizerInfo.virtualizer = "qemu-kvm" + return rsp + } + + spec.simulator(KVMConstant.KVM_VM_UPDATE_CPU_QUOTA_PATH) { + return new KVMAgentCommands.UpdateVmCpuQuotaRsp() + } + + spec.simulator(KVMConstant.KVM_VM_UPDATE_PRIORITY_PATH) { + return new KVMAgentCommands.UpdateVmPriorityRsp() + } + + spec.simulator(KVMConstant.KVM_VM_CHECK_STATE) { HttpEntity e -> + KVMAgentCommands.CheckVmStateCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.CheckVmStateCmd.class) + List vms = Q.New(VmInstanceVO.class).in(VmInstanceVO_.uuid, cmd.vmUuids).list() + KVMAgentCommands.CheckVmStateRsp rsp = new KVMAgentCommands.CheckVmStateRsp() + rsp.states = [:] + vms.each { + def kstate = KVMConstant.KvmVmState.fromVmInstanceState(it.state) + if (kstate != null) { + rsp.states[(it.uuid)] = kstate.toString() + } else { + rsp.states[(it.uuid)] = KVMConstant.KvmVmState.Shutdown.toString() + } + } + + return rsp + } + + spec.simulator(KVMConstant.KVM_ATTACH_NIC_PATH) { HttpEntity e -> + KVMAgentCommands.AttachNicCommand cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.AttachNicCommand.class) + KVMAgentCommands.AttachNicResponse rsp = new KVMAgentCommands.AttachNicResponse() + VirtualDeviceInfo deviceInfo = new VirtualDeviceInfo() + deviceInfo.setResourceUuid(cmd.nic.getUuid()) + rsp.setVirtualDeviceInfoList(Arrays.asList(deviceInfo)) + return rsp + } + + spec.simulator(KVMConstant.KVM_DETACH_NIC_PATH) { + return new KVMAgentCommands.DetachNicRsp() + } + + spec.simulator(KVMConstant.KVM_UPDATE_NIC_PATH) { + return new KVMAgentCommands.UpdateNicRsp() + } + + spec.simulator(KVMConstant.KVM_ATTACH_ISO_PATH) { + return new KVMAgentCommands.AttachIsoRsp() + } + + spec.simulator(KVMConstant.KVM_DETACH_ISO_PATH) { + return new KVMAgentCommands.DetachIsoRsp() + } + + spec.simulator(KVMConstant.KVM_MERGE_SNAPSHOT_PATH) { HttpEntity e, EnvSpec espec -> + return new KVMAgentCommands.MergeSnapshotRsp() + } + + VFS.vfsHook(KVMConstant.KVM_MERGE_SNAPSHOT_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> + KVMAgentCommands.MergeSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.MergeSnapshotCmd.class) + VolumeInventory volume = null + String primaryStorageType = null + + new SQLBatch() { + @Override + protected void scripts() { + VolumeVO vol = sql("select vol from VolumeVO vol where vol.vmInstanceUuid = :vmUuid " + + " and vol.deviceId = :deviceId", VolumeVO.class) + .param("vmUuid", cmd.vmUuid) + .param("deviceId", cmd.volume.getDeviceId()) + .find() + + assert vol: "cannot find dest volume[path: ${cmd.destPath}, deviceId: ${cmd.volume.getDeviceId()}] of VM[uuid: ${cmd.vmUuid}] in database" + volume = vol.toInventory() + + primaryStorageType = q(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).eq(PrimaryStorageVO_.uuid, vol.primaryStorageUuid).findValue() + assert primaryStorageType: "cannot find primary storage[uuid: ${vol.primaryStorageUuid}]" + } + }.execute() + + VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) + bkd.mergeSnapshots(e, espec, cmd, volume) + } + + spec.simulator(KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH) { HttpEntity e, EnvSpec espec -> + return new KVMAgentCommands.TakeSnapshotResponse() + } + + VFS.vfsHook(KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> + KVMAgentCommands.TakeSnapshotCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.TakeSnapshotCmd.class) + + VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volumeUuid).find() + assert volume: "cannot find volume[uuid: ${cmd.volumeUuid}]" + String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) + .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() + assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] from volume[uuid: ${volume.uuid}, name: ${volume.name}]" + VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) + + VFSSnapshot snapshot = bkd.takeSnapshot(e, espec, cmd, volume.toInventory() as VolumeInventory) + rsp.newVolumeInstallPath = snapshot.installPath + rsp.snapshotInstallPath = cmd.volumeInstallPath + // size required greater than 0, if no mock value set keep size return 1 + rsp.size = snapshot.size == null || snapshot.size == 0 ? 1 : snapshot.size + return rsp + } + + spec.simulator(KVMConstant.KVM_PING_PATH) { HttpEntity e, EnvSpec espec -> + KVMAgentCommands.PingCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.PingCmd.class) + assert null != cmd + assert null != cmd.hostUuid + + def rsp = new KVMAgentCommands.PingResponse() + rsp.hostUuid = cmd.hostUuid + rsp.version = connectCmdConcurrentHashMap.get(rsp.hostUuid).version + rsp.sendCommandUrl = connectCmdConcurrentHashMap.get(rsp.hostUuid).sendCommandUrl + return rsp + } + + spec.simulator(KVMConstant.KVM_UPDATE_HOST_CONFIGURATION_PATH) { HttpEntity e, EnvSpec espec -> + def rsp = new KVMAgentCommands.UpdateHostConfigurationResponse() + return rsp + } + + spec.simulator(KVMConstant.KVM_CONNECT_PATH) { HttpEntity e -> + Spec.checkHttpCallType(e, true) + KVMAgentCommands.ConnectCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.ConnectCmd.class) + + connectCmdConcurrentHashMap.put(cmd.hostUuid, cmd) + + def rsp = new KVMAgentCommands.ConnectResponse() + rsp.success = true + rsp.libvirtVersion = "1.0.0" + rsp.qemuVersion = "1.3.0" + return rsp + } + + spec.simulator(KVMConstant.KVM_CREATE_ENVELOPE_KEY_PATH) { + def rsp = new CreatePublicKeyResponse() + rsp.success = true + return rsp + } + + spec.simulator(KVMConstant.KVM_GET_ENVELOPE_KEY_PATH) { + def rsp = new GetPublicKeyResponse() + rsp.success = true + rsp.publicKey = MOCK_ENVELOPE_PUBLIC_KEY_BASE64 + return rsp + } + + spec.simulator(KVMConstant.KVM_VERIFY_ENVELOPE_KEY_PATH) { + def rsp = new VerifyPublicKeyResponse() + rsp.success = true + return rsp + } + + spec.simulator(KVMConstant.KVM_ROTATE_ENVELOPE_KEY_PATH) { + def rsp = new RotatePublicKeyResponse() + rsp.success = true + return rsp + } + + spec.simulator(KVMConstant.KVM_ENSURE_SECRET_PATH) { HttpEntity e -> + String hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + SecretHostDefineCmd cmd = JSONObjectUtil.toObject(e.body, SecretHostDefineCmd.class) + String cacheKey = buildHostSecretCacheKey(hostUuid, cmd?.vmUuid, cmd?.purpose, cmd?.keyVersion, cmd?.usageInstance) + def rsp = new SecretHostDefineResponse() + rsp.secretUuid = ensureSecretUuidCache.computeIfAbsent(cacheKey) { Platform.uuid } + return rsp + } + + spec.simulator(KVMConstant.KVM_GET_SECRET_PATH) { HttpEntity e -> + String hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + SecretHostGetCmd cmd = JSONObjectUtil.toObject(e.body, SecretHostGetCmd.class) + String cacheKey = buildHostSecretCacheKey(hostUuid, cmd?.vmUuid, cmd?.purpose, cmd?.keyVersion, cmd?.usageInstance) + def rsp = new SecretHostGetResponse() + String secretUuid = ensureSecretUuidCache.get(cacheKey) + if (secretUuid == null) { + rsp.setError("KEY_AGENT_SECRET_NOT_FOUND") + } else { + rsp.secretUuid = secretUuid + } + return rsp + } + + spec.simulator(KVMConstant.KVM_DELETE_SECRET_PATH) { HttpEntity e -> + String hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + SecretHostDeleteCmd cmd = JSONObjectUtil.toObject(e.body, SecretHostDeleteCmd.class) + String cacheKey = buildHostSecretCacheKey(hostUuid, cmd?.vmUuid, cmd?.purpose, cmd?.keyVersion, cmd?.usageInstance) + ensureSecretUuidCache.remove(cacheKey) + return new SecretHostDeleteResponse() + } + + spec.simulator(KVMConstant.KVM_ECHO_PATH) { HttpEntity e -> + Spec.checkHttpCallType(e, true) + return [:] + } + + spec.simulator(KVMConstant.KVM_DETACH_VOLUME) { + return new KVMAgentCommands.DetachDataVolumeResponse() + } + + spec.simulator(KVMConstant.KVM_VM_SYNC_PATH) { HttpEntity e -> + def hostUuid = e.getHeaders().getFirst(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID) + + List states = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.uuid, VmInstanceVO_.state) + .in(VmInstanceVO_.state, [VmInstanceState.Running, VmInstanceState.Unknown]) + .eq(VmInstanceVO_.hostUuid, hostUuid).listTuple() + + def rsp = new KVMAgentCommands.VmSyncResponse() + rsp.states = [:] + states.each { + String vmUuid = it.get(0, String.class) + VmInstanceState state = it.get(1, VmInstanceState.class) + if (state == VmInstanceState.Unknown) { + // host reconnecting will set VMs to Unknown in DB + // the spec.simulator treat them as Running by default + rsp.states[(vmUuid)] = KVMConstant.KvmVmState.Running.toString() + } else { + rsp.states[(vmUuid)] = KVMConstant.KvmVmState.fromVmInstanceState(state).toString() + } + } + rsp.setVmInShutdowns(new ArrayList()) + return rsp + } + + spec.simulator(KVMConstant.KVM_VOLUME_SYNC_PATH) { + return new VolumeSyncRsp() + } + + spec.simulator(KVMConstant.KVM_ATTACH_VOLUME) { HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.AttachDataVolumeCmd.class) + // assume all data volumes has same deviceType. + if (Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type).listValues().stream().distinct().count() == 1) { + assert (cmd.addons["attachedDataVolumes"] as List).stream() + .allMatch({ vol -> vol.deviceType == cmd.volume.deviceType }) + } + return new KVMAgentCommands.AttachDataVolumeResponse() + } + + spec.simulator(KVMConstant.KVM_CHECK_PHYSICAL_NETWORK_INTERFACE_PATH) { HttpEntity e -> + Spec.checkHttpCallType(e, true) + return new KVMAgentCommands.CheckPhysicalNetworkInterfaceResponse() + } + + spec.simulator(KVMConstant.KVM_ADD_INTERFACE_TO_BRIDGE_PATH) { + return new KVMAgentCommands.AddInterfaceToBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_REALIZE_L2NOVLAN_NETWORK_PATH) { + return new KVMAgentCommands.CreateBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_MIGRATE_VM_PATH) { + return new KVMAgentCommands.MigrateVmResponse() + } + + spec.simulator(KVMConstant.KVM_GET_CPU_XML_PATH) { + return new KVMAgentCommands.VmGetCpuXmlResponse() + } + + spec.simulator(KVMConstant.KVM_COMPARE_CPU_FUNCTION_PATH) { + return new KVMAgentCommands.VmCompareCpuFunctionResponse() + } + + spec.simulator(KVMConstant.KVM_UPDATE_L2VLAN_NETWORK_PATH) { + return new KVMAgentCommands.UpdateL2NetworkResponse() + } + + spec.simulator(KVMConstant.KVM_UPDATE_L2VXLAN_NETWORK_PATH) { + return new KVMAgentCommands.UpdateL2NetworkResponse() + } + + spec.simulator(KVMConstant.KVM_CHECK_L2NOVLAN_NETWORK_PATH) { + return new KVMAgentCommands.CheckBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_CHECK_L2VLAN_NETWORK_PATH) { + return new KVMAgentCommands.CheckVlanBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_REALIZE_L2VLAN_NETWORK_PATH) { + return new KVMAgentCommands.CreateVlanBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_CHECK_OVSDPDK_NETWORK_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_REALIZE_OVSDPDK_NETWORK_PATH) { + return new KVMAgentCommands.CreateBridgeResponse() + } + + spec.simulator(KVMConstant.KVM_GENERATE_VDPA_PATH) { HttpEntity e -> + KVMAgentCommands.GenerateVdpaCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.GenerateVdpaCmd.class) + def rsp = new KVMAgentCommands.GenerateVdpaResponse() + + ArrayList nics = cmd.getNics() + def vdpaPaths = new ArrayList() + + nics.each { KVMAgentCommands.NicTO it -> + vdpaPaths.add("/var/run/zstack-vdpa/" + it.bridgeName + "/" + it.nicInternalName) + } + rsp.setVdpaPaths(vdpaPaths) + + return rsp + } + + spec.simulator(KVMConstant.KVM_DELETE_VDPA_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_GENERATE_VHOST_USER_CLIENT_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_DELETE_VHOST_USER_CLIENT_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_SYNC_VM_DEVICEINFO_PATH) { HttpEntity e -> + SyncVmDeviceInfoCmd cmd = JSONObjectUtil.toObject(e.body, SyncVmDeviceInfoCmd.class) + def rsp = new SyncVmDeviceInfoResponse() + + rsp.virtualizerInfo = new VirtualizerInfoTO() + rsp.virtualizerInfo.uuid = cmd.vmInstanceUuid + rsp.virtualizerInfo.virtualizer = "qemu-kvm" + rsp.virtualizerInfo.version = "4.2.0-632.g6a6222b.el7" + + return rsp + } + + spec.simulator(KVMConstant.KVM_START_VM_PATH) { HttpEntity e -> + StartVmCmd cmd = JSONObjectUtil.toObject(e.body, StartVmCmd.class) + assert new HashSet<>(cmd.dataVolumes.deviceId).size() == cmd.dataVolumes.size() + StartVmResponse rsp = new StartVmResponse() + rsp.virtualDeviceInfoList = [] + List pciInfo = new ArrayList() + pciInfo.add(cmd.rootVolume) + pciInfo.addAll(cmd.dataVolumes) + + Integer counter = 0 + pciInfo.each { to -> + VirtualDeviceInfo info = new VirtualDeviceInfo() + info.resourceUuid = to.volumeUuid + info.deviceAddress = new DeviceAddress() + info.deviceAddress.domain = "0000" + info.deviceAddress.bus = "00" + info.deviceAddress.slot = Integer.toHexString(counter) + info.deviceAddress.function = "0" + + counter++ + + rsp.virtualDeviceInfoList.add(info) + } + + rsp.virtualizerInfo = new VirtualizerInfoTO() + rsp.virtualizerInfo.uuid = cmd.vmInstanceUuid + rsp.virtualizerInfo.virtualizer = "qemu-kvm" + rsp.virtualizerInfo.version = "4.2.0-632.g6a6222b.el7" + rsp.edkRpm = "edk2-ovmf-20220126gitbb1bba3d77-3.el8.noarch" + + return rsp + } + + spec.simulator(KVMConstant.KVM_STOP_VM_PATH) { + return new KVMAgentCommands.StopVmResponse() + } + + spec.simulator(KVMConstant.KVM_PAUSE_VM_PATH) { + return new KVMAgentCommands.PauseVmResponse() + } + + spec.simulator(KVMConstant.KVM_RESUME_VM_PATH) { + return new KVMAgentCommands.ResumeVmResponse() + } + + spec.simulator(KVMConstant.KVM_REBOOT_VM_PATH) { + return new KVMAgentCommands.RebootVmResponse() + } + + spec.simulator(KVMConstant.KVM_DESTROY_VM_PATH) { + return new KVMAgentCommands.DestroyVmResponse() + } + + spec.simulator(KVMConstant.KVM_GET_VNC_PORT_PATH) { + def rsp = new KVMAgentCommands.GetVncPortResponse() + rsp.port = 5900 + return rsp + } + + spec.simulator(KVMConstant.KVM_LOGOUT_ISCSI_PATH) { + return new KVMAgentCommands.LogoutIscsiTargetRsp() + } + + spec.simulator(KVMConstant.KVM_LOGIN_ISCSI_PATH) { + return new KVMAgentCommands.LoginIscsiTargetRsp() + } + + spec.simulator(KVMConstant.KVM_VM_ONLINE_INCREASE_CPU) { + return new KVMAgentCommands.IncreaseCpuResponse() + } + + spec.simulator(KVMConstant.KVM_VM_ONLINE_INCREASE_MEMORY) { + return new KVMAgentCommands.IncreaseMemoryResponse() + } + + spec.simulator(KVMConstant.KVM_UPDATE_HOST_OS_PATH) { + return new KVMAgentCommands.UpdateHostOSRsp() + } + + spec.simulator(KVMConstant.KVM_HOST_UPDATE_DEPENDENCY_PATH) { + return new KVMAgentCommands.UpdateDependencyRsp() + } + + spec.simulator(KVMConstant.HOST_UPDATE_SPICE_CHANNEL_CONFIG_PATH) { + return new KVMAgentCommands.UpdateSpiceChannelConfigResponse() + } + + spec.simulator(KVMConstant.KVM_SCAN_VM_PORT_STATUS) { + return new KVMAgentCommands.ScanVmPortResponse() + } + + spec.simulator(KVMConstant.GET_DEV_CAPACITY) { + KVMAgentCommands.GetDevCapacityResponse rsp = new KVMAgentCommands.GetDevCapacityResponse() + rsp.totalSize = SizeUnit.GIGABYTE.toByte(100) + rsp.availableSize = SizeUnit.GIGABYTE.toByte(80) + return rsp + } + + spec.simulator(KVMConstant.KVM_CONFIG_PRIMARY_VM_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_CONFIG_SECONDARY_VM_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_START_COLO_SYNC_PATH) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_REGISTER_PRIMARY_VM_HEARTBEAT) { + return new KVMAgentCommands.AgentResponse() + } + + spec.simulator(KVMConstant.KVM_HOST_CHECK_FILE_PATH) { HttpEntity e -> + KVMAgentCommands.CheckFileOnHostCmd cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.CheckFileOnHostCmd.class) + KVMAgentCommands.CheckFileOnHostResponse response = new KVMAgentCommands.CheckFileOnHostResponse() + response.existPaths = new HashMap<>() + cmd.paths.forEach({ path -> response.existPaths.put(path, "") }) + return response + } + + spec.simulator(KVMConstant.KVM_HOST_NUMA_PATH) { + def rsp = new KVMAgentCommands.GetHostNUMATopologyResponse() + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_ATTACH_VOLUME_PATH) { + def rsp = new KVMAgentCommands.AttachVolumeRsp() + rsp.device = "/dev/nbd0" + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_DETACH_VOLUME_PATH) { + def rsp = new KVMAgentCommands.DetachVolumeRsp() + return rsp + } + + spec.simulator(KVMConstant.KVM_BLOCK_COMMIT_VOLUME_PATH) { HttpEntity e -> + def rsp = new BlockCommitResponse() + rsp.size = 1 + return rsp + } + + VFS.vfsHook(KVMConstant.KVM_BLOCK_COMMIT_VOLUME_PATH, spec) { rsp, HttpEntity e, EnvSpec espec -> + BlockCommitCmd cmd = JSONObjectUtil.toObject(e.body, BlockCommitCmd.class) + + VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volume.getVolumeUuid()).find() + assert volume: "cannot find volume[uuid: ${cmd.volume.getVolumeUuid()}]" + + String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) + .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() + assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] " + + "from volume[uuid: ${volume.uuid}, name: ${volume.name}]" + + VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) + bkd.blockCommit(e, espec, cmd, volume.toInventory() as VolumeInventory) + return rsp + } + + spec.simulator(KVMConstant.KVM_BLOCK_PULL_VOLUME_PATH) { HttpEntity e -> + def rsp = new BlockPullResponse() + rsp.size = 1 + return rsp + } + + VFS.vfsHook(KVMConstant.KVM_BLOCK_PULL_VOLUME_PATH, spec) { BlockPullResponse rsp, HttpEntity e, EnvSpec espec -> + BlockPullCmd cmd = JSONObjectUtil.toObject(e.body, BlockPullCmd.class) + + VolumeVO volume = Q.New(VolumeVO.class).eq(VolumeVO_.uuid, cmd.volume.getVolumeUuid()).find() + assert volume: "cannot find volume[uuid: ${cmd.getVolume().getVolumeUuid()}]" + + String primaryStorageType = Q.New(PrimaryStorageVO.class).select(PrimaryStorageVO_.type) + .eq(PrimaryStorageVO_.uuid, volume.primaryStorageUuid).findValue() + assert primaryStorageType: "cannot find primary storage[uuid: ${volume.primaryStorageUuid}] " + + "from volume[uuid: ${volume.uuid}, name: ${volume.name}]" + + VFSPrimaryStorageTakeSnapshotBackend bkd = getVFSPrimaryStorageTakeSnapshotBackend(primaryStorageType) + + Qcow2 newInstallPath = bkd.blockPull(e, espec, cmd, volume.toInventory() as VolumeInventory) + rsp.size = newInstallPath.actualSize == 0 ? 1 : newInstallPath.actualSize + return rsp + } + + spec.simulator(KVMConstant.TAKE_VM_CONSOLE_SCREENSHOT_PATH) { + def rsp = new KVMAgentCommands.TakeVmConsoleScreenshotRsp() + rsp.imageData = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABECAIAAADFvmZTAAACA0lEQVR4nO3VMYviQBQH8Hkvk5hE2ZUEU+juFlbKauH3/wYWNhYWKiiIRBDcZDckJJmZK6Zb7q44uJW7/f+qCcOQzP89XoSAL0FPT09VVTFzEARhGKZp2rat67qO4xhjlFJ5nidJcr1eHx8fPc+r65qZ27Zt2zbP83t//z9DRlFUVRURGWN83/c8z/O8Xq/HzFEUvb29aa211sxsgxZCKKWIKMuyPM9tSWypHMdRSmmtP72DmTudTlmWRCSl1Forpe5x2XuSSZJcLpeiKPr9flEUdV0LIZqm+fj4GI/H+/1+Npsdj8fX11chRFVV5/N5MBgIIZg5juP5fN7tdpfL5WAwiKJovV4Ph8MgCDabzWQyKcvycDgsFoskSVarlZTy+fl5t9udz2db2jvf/gsRETGz1tp13bZtbT8yszHGdqiNw66Z2W4ZY7TWUkqbl5TSGGOPSymFEHb+2Oa1u47jEFHTNFrrbxWxRQ8PD1LKuq7Lsux2u8zcNE1d10EQFEXR6XRc1yWiPM9937cxua6rlPrplIBfkS8vL2EYZlmWpul0OvV9/3Q6vb+/j0aj4/EYx3EYhkqp7XY7HA6TJDHG3G43IjocDnbsfsP2/OuIyP4P4U8Q0W8eAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgf/QDviYImvcayGAAAAAASUVORK5CYII=" + return rsp + } + + spec.simulator(KVMConstant.KVM_UPDATE_HOST_NQN_PATH) { + def rsp = new KVMAgentCommands.UpdateHostNqnRsp() + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PATH) { + DownloadFileResponse rsp = new DownloadFileResponse() + rsp.md5sum = "00df1327d49e4631a21f4467aa729c11" + rsp.size = 1024 + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_FILE_UPLOAD_PATH) { + UploadFileResponse rsp = new UploadFileResponse() + rsp.directUploadPath = "http://172.1.1.1:7070/host/file/direct-upload" + return rsp + } + + spec.simulator(KVMConstant.KVM_HOST_FILE_DOWNLOAD_PROGRESS_PATH) { + GetDownloadFileProgressResponse rsp = new GetDownloadFileProgressResponse() + rsp.completed = false + rsp.downloadSize = 1 + rsp.size = 1024 + rsp.lastOpTime = System.currentTimeMillis() + return rsp + } + + spec.simulator(KVMConstant.KVM_UPDATE_HOSTNAME_PATH) { + return new UpdateHostnameRsp() + } + + spec.simulator(KVMConstant.READ_VM_HOST_FILE_PATH) { HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, ReadVmHostFileContentCmd) + + def rsp = new ReadVmHostFileContentResponse() + for (final def param in cmd.hostFiles) { + def to = new VmHostFileTO() + to.path = param.path + to.type = param.type + to.fileFormat = VmHostFileContentFormat.Raw.toString() + to.contentBase64 = "dGVzdA==" + rsp.hostFiles.add(to) + } + return rsp + } + + spec.simulator(KVMConstant.WRITE_VM_HOST_FILE_PATH) { HttpEntity e -> + return new WriteVmHostFileContentResponse() + } + + spec.simulator(KVMConstant.BACKUP_VM_HOST_FILE_PATH) { HttpEntity e -> + return new BackupVmHostFileResponse() + } + } + + /** Resets simulated host vTPM secret entries for getSecret / ensureSecret / deleteSecret. */ + static void resetSimulatedHostSecretCache() { + ensureSecretUuidCache.clear() + } + + static void putSimulatedHostSecretForTest(String hostUuid, String vmUuid, String purpose, + Integer keyVersion, String secretUuid) { + ensureSecretUuidCache.put(buildHostSecretCacheKey(hostUuid, vmUuid, purpose, keyVersion, KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM), secretUuid) + } + + static String getSimulatedHostSecretForTest(String hostUuid, String vmUuid, String purpose, Integer keyVersion) { + return ensureSecretUuidCache.get(buildHostSecretCacheKey(hostUuid, vmUuid, purpose, keyVersion, KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM)) + } + + private static String buildHostSecretCacheKey(String hostUuid, String vmUuid, String purpose, Integer keyVersion, String usageInstance) { + String usage = usageInstance ?: KVMConstant.HOST_SECRET_USAGE_INSTANCE_VTPM + return String.format("%s::%s::%s::%s::%s", + hostUuid ?: "", + vmUuid ?: "", + purpose ?: "", + keyVersion == null ? "" : String.valueOf(keyVersion), + usage) + } +} diff --git a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy index 747ca4388b1..8997d7d27e6 100755 --- a/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/LocalStorageSpec.groovy @@ -600,6 +600,26 @@ 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) { + return new LocalStorageKvmBackend.GetVmInstanceMetadataRsp() + } + + simulator(LocalStorageKvmBackend.SCAN_VM_METADATA_PATH) { + return new LocalStorageKvmBackend.ScanVmMetadataRsp() + } + + simulator(LocalStorageKvmBackend.CLEANUP_VM_METADATA_PATH) { + return new LocalStorageKvmBackend.CleanupVmMetadataRsp() + } + + simulator(LocalStorageKvmBackend.PREFIX_REBASE_BACKING_FILES_PATH) { + return new LocalStorageKvmBackend.PrefixRebaseBackingFilesRsp() + } } } diff --git a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy index 0dbe59bfc01..3b7d7ad105e 100755 --- a/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/NfsPrimaryStorageSpec.groovy @@ -522,6 +522,26 @@ 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() + } + + simulator(NfsPrimaryStorageKVMBackend.NFS_PREFIX_REBASE_BACKING_FILES_PATH) { + return new NfsPrimaryStorageKVMBackendCommands.PrefixRebaseBackingFilesRsp() + } + VFS.vfsHook(NfsPrimaryStorageKVMBackend.NFS_REBASE_VOLUME_BACKING_FILE_PATH, xspec) { rsp, HttpEntity e, EnvSpec spec -> def cmd = JSONObjectUtil.toObject(e.body, NfsPrimaryStorageKVMBackendCommands.NfsRebaseVolumeBackingFileCmd.class) diff --git a/utils/src/main/java/org/zstack/utils/CollectionUtils.java b/utils/src/main/java/org/zstack/utils/CollectionUtils.java index 8ddb29b647e..12a7ed72c61 100755 --- a/utils/src/main/java/org/zstack/utils/CollectionUtils.java +++ b/utils/src/main/java/org/zstack/utils/CollectionUtils.java @@ -4,6 +4,7 @@ import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; +import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -56,6 +57,7 @@ public static List filter(Collection from, Predicate tester) { return from.stream().filter(tester).collect(Collectors.toList()); } + @Nullable public static T findOneOrNull(Collection from, Predicate tester) { return from.stream().filter(tester).findFirst().orElse(null); } diff --git a/utils/src/main/java/org/zstack/utils/StringDSL.java b/utils/src/main/java/org/zstack/utils/StringDSL.java index 54cbf4135b8..705b88a8b4d 100755 --- a/utils/src/main/java/org/zstack/utils/StringDSL.java +++ b/utils/src/main/java/org/zstack/utils/StringDSL.java @@ -150,8 +150,12 @@ public static boolean isApiId(String apiId) { } public static String createFixedUuid(Class voClass) { + return createFixedUuid(voClass.getSimpleName()); + } + + public static String createFixedUuid(String text) { StringBuilder builder = new StringBuilder( - UUID.nameUUIDFromBytes(voClass.getSimpleName().getBytes()).toString().replace("-", "")); + UUID.nameUUIDFromBytes(text.getBytes()).toString().replace("-", "")); builder.setCharAt(0, 'f'); builder.setCharAt(1, 'f'); builder.setCharAt(2, '0');